﻿# 12 мгновений опечаток и копипаста, или почему сбоит AI: проверяем код OpenVINO

"OpenVINO — набор инструментов, позволяющий проводить глубокое обучение AI для взаимодействия с реальным миром, теперь ещё эффективнее\!" — эта новость для нас прозвучала как призыв к действию\. Код проекта проверен, ошибки найдены, и первая часть статьи готова к прочтению\. Будет интересно\!

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

## Несколько слов о проекте

6 марта 2024 года Intel выпустила Open Source решение OpenVINO версии 2024\.0, которое получило множество новых улучшений\! Подробнее почитать о проекте вы можете [здесь](https://docs.openvino.ai/2024/index.html)\. Что же оно из себя представляет? Прежде всего, это инструмент, помогающий специалистам более эффективно обучать AI компьютерному зрению\.

<details>
   <summary>А если не знаете, что такое компьютерное зрение, вам сюда\\\.</summary>

Компьютерное зрение — это когда с помощью более или менее нормальной камеры, подключённой к компьютеру, мы можем распознать на видео какие\-то необходимые нам данные\. Например, мы хотим поймать в объектив белого кролика посреди таких же белых сугробов зимой\. Глазами это будет сделать крайне проблематично, но с помощью камеры и обученного AI любой кролик, пробегающий мимо, не останется незамеченным\.

Стоит также отметить, что компьютерное зрение штука крайне полезная, и помогает во многих областях, например:

1. Сортировка и классификация сельскохозяйственных продуктов;
1. Классификация и выявления дефектов на производстве у различных электронных модулей и компонентов;
1. Промышленная автоматизация;
1. Системы распознавания лиц;
1. Использование AI в колоноскопии, при ухудшении зрения или же при выявлении заболеваний мозга\.

Это не полный список возможностей, но как бы и статья не про это\. В любом случае, полезность использования технологий искусственного интеллекта неоспорима, и для того, чтобы всё это стало возможным, разрабатываются различные инструменты углублённого обучения\. Одним из таких инструментов как раз\-таки и является OpenVINO\.

```cpp
```


</details>


Если точнее, OpenVINO — это бесплатный набор готовых утилит, которые разработчики могут использовать для ускорения разработки подобных решений\. Так же, помимо инструментов обучения, есть ещё инструменты для тестирования эффективности AI и свой бенчмарк\. Как\-то так\.

И вот, в робкой попытке прикоснуться к прекрасному своими кривульками, я вновь расчехлил статический анализатор PVS\-Studio, дабы окунуться в код проекта для поиска инородных сущностей, называемых багами\. Как я уже написал ранее, будет интересно\.

Однако, несмотря на баги, инструмент свои функции выполняет: AI обучается и работает \(если судить по многочисленным статьям разработчиков\)\. 

Нижеописанной ситуации в практике обучения своего AI вы не встретите:

> Разработчик: AI, я компьютерного зрения не чувствую\!
>
> AI: Мяу?\!

Коммит, на котором я собирал проект для проверки: [2d8ac08](https://github.com/openvinotoolkit/openvino/tree/2d8ac08bf1f87f8ac455eae381213b52e781fe8c)\.

<details>
   <summary>Дисклеймер</summary>

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


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

Разбираемый проект оказался богат на опечатки и проблемы, которые возникают после copy\-paste\. 

Исходя из этого и было принято решение написать отдельную статью, дабы подчеркнуть тот факт, что ошибки такого рода встречаются практически везде\. 

И да, всегда есть вероятность того, что так и было задумано программистом, но, сия вероятность\.\.\. крайне мала: "Иммолейт импрувед\!"

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

```cpp
ov::pass::ConvertPadToGroupConvolution::
                       ConvertPadToGroupConvolution() 
{
  ....
  const auto& pad_begin = pad->get_pads_begin();
  const auto& pad_end = pad->get_pads_end();

  if (pad_begin.empty() || pad_end.empty()) 
  {
    // pads will be empty if inputs are not constants
    return false;
  }

  // check that Pad has non-negative values
  auto pred = [](int64_t a) 
              {
                return a < 0;
              };
  if (std::any_of(pad_begin.begin(), pad_begin.end(), pred) ||
      std::any_of(pad_begin.begin(), pad_begin.end(), pred))  // <=
  { 
    return false;
  }
  ....
}
```

Предупреждение анализатора: 

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions 'std::any\_of\(pad\_begin\.begin\(\), pad\_begin\.end\(\), pred\)' to the left and to the right of the '\|\|' operator\. [convert\_pad\_to\_group\_conv\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/common/transformations/src/transformations/op_conversions/convert_pad_to_group_conv.cpp) 66

Как вы можете заметить, в самом нижнем условии сравниваются два одинаковых выражения: одни и те же вызовы функции _std::any\_of_ с одинаковыми параметрами\. Код выше добавлен в пример не просто так, ведь если посмотреть на него, можно сделать вывод, что условие, скорее всего, должно быть написано следующим образом:

```cpp
if (std::any_of(pad_begin.begin(), pad_begin.end(), pred) ||
    std::any_of(pad_end.begin(), pad_end.end(), pred)) 
{    
  return false;
}
```

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

```cpp
ov::pass::ShuffleChannelsFusion::
                   ShuffleChannelsFusion(const bool reshape_constants_check)
{
  ....
  auto reshape_before_constant = std::dynamic_pointer_cast
                                       <ov::op::v0::Constant>(
       pattern_map.at(reshape_before_const_pattern).get_node_shared_ptr());

  auto reshape_before = std::dynamic_pointer_cast<ov::op::v1::Reshape>(
       pattern_map.at(reshape_before_pattern).get_node_shared_ptr());

  auto transpose = std::dynamic_pointer_cast<ov::op::v1::Transpose>(
       pattern_map.at(transpose_pattern).get_node_shared_ptr());

  auto reshape_after = std::dynamic_pointer_cast<ov::op::v1::Reshape>(
       pattern_map.at(reshape_after_pattern).get_node_shared_ptr());

  auto reshape_after_constant = std::dynamic_pointer_cast<ov::op::v0::Constant>(
       pattern_map.at(reshape_after_const_pattern).get_node_shared_ptr());

  if (!reshape_after || !transpose || !reshape_after ||      // <=
      !reshape_before_constant || !reshape_after_constant) 
  {
    return false;
  }
  ....
}
```

Предупреждение анализатора: 

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions '\!reshape\_after' to the left and to the right of the '\|\|' operator\. [shuffle\_channels\_fusion\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/common/transformations/src/transformations/common_optimizations/shuffle_channels_fusion.cpp) 115

Снова ошибка в условии\. Два раза проверяется одно и то же выражение, а именно, что указатель _reshape\_after_ ненулевой\. Если снова посмотрим на код выше, заметим инициализацию _reshape\_before_\. Скорее всего, условие должно быть переписано следующим образом:

```cpp
if (!reshape_after || !transpose || !reshape_before ||
    !reshape_before_constant || !reshape_after_constant) 
{
    return false;
}
```

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

```cpp
....
using PatternValueMaps = std::vector<PatternValueMap>;
....
PatternValueMaps m_pattern_value_maps;
....
MatcherState::~MatcherState() 
{
  if (m_restore) 
  {
    if (!m_matcher->m_matched_list.empty())
    {
      m_matcher->m_matched_list.erase(m_matcher->m_matched_list.begin() + 
                                      m_watermark,
                                      m_matcher->m_matched_list.end());
    }
    if (!m_pattern_value_maps.empty())
    {
      m_matcher->m_pattern_value_maps.erase(m_pattern_value_maps.begin() + // <=
                                            m_capture_size,
                                            m_pattern_value_maps.end());
    }
    m_matcher->m_pattern_map = m_pattern_value_map;
  }
}
```

Предупреждение анализатора: 

[V539](https://pvs-studio.ru/ru/docs/warnings/v539/) \[CERT\-CTR53\-CPP\] Consider inspecting iterators which are being passed as arguments to function 'erase'\. [matcher\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/core/src/pattern/matcher.cpp) 48

На этот раз ошибка комплексная\. Рассмотрим всё по порядку\.

Вначале проверяется, что вектор _m\_pattern\_value\_maps_ не пустой\. 

Затем мы замечаем, что в _then_\-ветке второго вложенного _if_ почему\-то происходит работа уже с другим вектором:_ m\_matcher\-\>m\_pattern\_value\_maps_\. Дальше — больше\. 

В функцию\-член _std::vector<PatternValueMap\>::erase_ контейнера _m\_matcher\-\>m\_pattern\_value\_maps_ передаются аргументы в виде итераторов другого контейнера — _m\_pattern\_value\_maps_\. И работать это корректно не будет\.

Судя по [конструктору](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/core/src/pattern/matcher.cpp#L19-L23) и [деструктору](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/core/src/pattern/matcher.cpp#L40-L54), класс _MatcherState_ предназначен для отката изменений [объекта](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/core/include/openvino/pass/pattern/matcher.hpp#L35) типа _Matcher_\. Возможно, раньше код _RAII_\-обёртки сохранял состояние объекта типа _Matcher_ в [полях](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/core/include/openvino/pass/pattern/matcher.hpp#L36-L37) _MatcherState::m\_pattern\_value\_map_ и _MatcherState::m\_pattern\_value\_maps_ и затем возвращал в деструкторе\.

Однако потом код переписали, добавив [поля](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/core/include/openvino/pass/pattern/matcher.hpp#L38-L39) _MatcherState::m\_watermark_ и _MatcherState::m\_capture\_size_\. Они ответственны за удаление элементов, которые были добавлены в конец [контейнеров](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/core/include/openvino/pass/pattern/matcher.hpp#L177-L178) _Matcher::m\_matched\_list_ и _Matcher::m\_pattern\_value\_maps_\.

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

```cpp
if (!m_matcher->m_pattern_value_maps.empty())
{
  m_matcher->m_pattern_value_maps.erase(
                                  m_matcher->m_pattern_value_maps.begin() +
                                  m_capture_size,
                                  m_matcher->m_pattern_value_maps.end()     );
}
```

Также хочу отметить, что поле _MatcherState::m\_pattern\_value\_maps_ теперь не используется, и, возможно, его стоит удалить\.

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

```cpp
template <x64::cpu_isa_t isa>
void jit_power_dynamic_emitter::
              emit_isa(const std::vector<size_t> &in_vec_idxs, 
                       const std::vector<size_t> &out_vec_idxs) const 
{
  ....
  if (isa == x64::avx512_core || isa == x64::avx512_core)    // <=
  {
    h->sub(h->rsp, n_k_regs_to_save * k_mask_size);
    for (size_t i = 0; i < n_k_regs_to_save; ++i) 
    {
      if (x64::mayiuse(x64::avx512_core))
        h->kmovq(h->ptr[h->rsp + i * k_mask_size], Opmask(i));
      else
        h->kmovw(h->ptr[h->rsp + i * k_mask_size], Opmask(i));
    }
  }
  ....
}
```

Предупреждение анализатора: 

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions 'isa \=\= x64::avx512\_core' to the left and to the right of the '\|\|' operator\. [jit\_eltwise\_emitters\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/intel_cpu/src/emitters/plugin/x64/jit_eltwise_emitters.cpp) 705

И снова ошибка в условии: проверяются два абсолютно одинаковых выражения\. Скорее всего, во втором выражении _isa_ должно быть равно другому значению\.

Примечательно, что эта ошибка встречается в коде дополнительно в трёх местах:

1. V501 There are identical sub\-expressions 'isa \=\= x64::avx512\_core' to the left and to the right of the '\|\|' operator\. jit\_eltwise\_emitters\.cpp 754
1. V501 There are identical sub\-expressions 'isa \=\= x64::avx512\_core' to the left and to the right of the '\|\|' operator\. jit\_eltwise\_emitters\.cpp 1609
1. V501 There are identical sub\-expressions 'isa \=\= x64::avx512\_core' to the left and to the right of the '\|\|' operator\. jit\_eltwise\_emitters\.cpp 1658



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

```cpp
void GridSampleKernel<isa>::reflectionPadding(const Vmm& vCoordDst, 
                                              const Vmm& vCoordOrigin, 
                                              const coord dim         ) 
{
  ....
  if (dim == coord::w) 
  {
    ....
  } else if (coord::h)                      // <=
  {
    ....         
  } else {....}
  ....
}
```

Предупреждение анализатора: 

[V768](https://pvs-studio.ru/ru/docs/warnings/v768/) The enumeration constant 'h' is used as a variable of a Boolean\-type\. [grid\_sample\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/intel_cpu/src/nodes/kernels/x64/grid_sample.cpp) 925

Очень странно, что во втором условии проверяется константа _coord::h_\. Конечно, она имеет значение 1, и такой код всегда будет возвращать _true_, что явно является ошибкой\. 

В этом случае код в теле последней ветки _else_ никогда не будет выполнен\. Что же это: хитрая задумка разработчика или, может, искусственное ограничение для выполняемого кода? Скорее похоже на баг, и в условии должно быть выражение _dim \=\= coord::h_\.

И ещё несколько подобных предупреждений:

1. V768 The enumeration constant 'h' is used as a variable of a Boolean\-type\. grid\_sample\.cpp 959
1. V768 The enumeration constant 'h' is used as a variable of a Boolean\-type\. grid\_sample\.cpp 990

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

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

```cpp
OutputVector translate_im2col(const NodeContext& context) 
{
  num_inputs_check(context, 5, 5);
  auto input = context.get_input(0);
  auto kernel_size = context.const_input<std::vector<int64_t>>(1);
  PYTORCH_OP_CONVERSION_CHECK(kernel_size.size() == 2, 
                              "kernel size should contains 2 elements");
  auto dilation = context.const_input<std::vector<int64_t>>(2);
  PYTORCH_OP_CONVERSION_CHECK(kernel_size.size() == 2,               // <=
                              "dilation should contains 2 elements");
  auto padding = context.const_input<std::vector<int64_t>>(3);
  PYTORCH_OP_CONVERSION_CHECK(kernel_size.size() == 2,               // <=
                              "padding should contains 2 elements");
  auto stride = context.const_input<std::vector<int64_t>>(4);
  PYTORCH_OP_CONVERSION_CHECK(kernel_size.size() == 2,               // <=
                              "stride should contains 2 elements");
  ....
}
```

Предупреждение анализатора: 

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'kernel\_size\.size\(\) \=\= 2' is always true\. [im2col\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/frontends/pytorch/src/op/im2col.cpp) 65

И ещё несколько предупреждений для понимания полной картины:

1. V547 Expression 'kernel\_size\.size\(\) \=\= 2' is always true\. im2col\.cpp 67
1. V547 Expression 'kernel\_size\.size\(\) \=\= 2' is always true\. im2col\.cpp 69

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

Первое, что бросается в глаза: проверка _kernel\_size\.size\(\) \=\= 2_ происходит аж целых четыре раза\. При этом после первой проверки вектор _kernel\_size_ нигде не изменяется\. На это нам анализатор и намекает, говоря о том, что следующие три проверки всегда истинны\. 

А как именно анализатор это понял? 

В макросе _PYTORCH\_OP\_CONVERSION\_CHECK_ есть [функция](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/frontends/common/src/exception.cpp#L40-L50) _create_, которая бросает исключение, если переданное в макросе выражение ложно\. Следовательно, чтобы весь код после первой проверки был достижим \(а по умолчанию мы считаем его таковым\), необходимо чтобы выражение _kernel\_size\.size\(\)_ было равно 2\.

Следующее, о чём мы могли бы задуматься: а зачем нам вообще проверять значение _kernel\_size\.size\(\),_ если вектор _kernel\_size_ не меняется, и его размер всегда будет равен 2? Всё, что мы рассмотрели ранее, было только следствием допущенной ошибки, а не причиной\. Причина же проста и заключается в следующем\.

Вот был создан и инициализирован объект _kernel\_size_, и следом в макрос _PYTORCH\_OP\_CONVERSION\_CHECK_ передано и проверено внутри выражение _kernel\_size\.size\(\) \=\= 2_\. Следом создан ещё один объект _dilation_, однако в макрос _PYTORCH\_OP\_CONVERSION\_CHECK_ так же передано выражение _kernel\_size\.size\(\) \=\= 2_, хотя,   если подумать, по логике, должно быть передано и проверено выражение _dilation\.size\(\) \=\= 2_\.

Так, же, если обратите внимание на второй аргумент, передающийся в этот макрос — строку, то, опять же, становится очевидно, что функция _size_ должна вызываться для объекта _dilation_\. И так же для двух других объектов, ну вы поняли\.\.\. программист копипастил строки кода и забыл поменять имена для объектов, проверки размера для которых передавал параметром в макрос\.

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

```cpp
void BinaryConvolution::createPrimitive() 
{
  ....
   bool args_ok = jcp.l_pad <= jcp.ur_w &&                                 // <=
          (r_pad_no_tail <= jcp.ur_w) && (jcp.l_pad <= jcp.ur_w) &&        // <=
            IMPLICATION(jcp.kw > 7, (jcp.t_pad == 0 && jcp.l_pad == 0) ||
              (jcp.stride_w == 1 && jcp.stride_h == 1));
  ....
}
```

Предупреждение анализатора: 

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions 'jcp\.l\_pad <\= jcp\.ur\_w' to the left and to the right of the '&&' operator\. [bin\_conv\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/intel_cpu/src/nodes/bin_conv.cpp) 1088

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

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

```cpp
void FakeQuantize::getSupportedDescriptors() 
{
  ....
  if (getInputShapeAtPort(0).getRank() != 
                       getInputShapeAtPort(0).getRank())    // <=
  {
    OPENVINO_THROW(errorPrefix, 
                   "has different ranks for input and output tensors");
  }
  ....
}
```

Предупреждение анализатора: 

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions 'getInputShapeAtPort\(0\)\.getRank\(\)' to the left and to the right of the '\!\=' operator\. [fake\_quantize\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/intel_cpu/src/nodes/fake_quantize.cpp) 1301

В условии проверяется на неравенство одно и то же подвыражение\. Скорее всего, здесь замешана проблема копипаста\. А так как нам прямым текстом пишут: "_has different ranks for input and output tensors_", — и если учесть тот факт, что существует аналогичная функция, но для _output_ значения:

```cpp
const Shape& getOutputShapeAtPort(size_t port) const 
{
  if (outputShapes.size() <= port) 
  {
    OPENVINO_THROW("Incorrect output port number for node ", getName());
  }
  return outputShapes[port];
}
```

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

```cpp
if (getInputShapeAtPort(0).getRank() != getOutputShapeAtPort(0).getRank())
{
  OPENVINO_THROW(errorPrefix, 
                 "has different ranks for input and output tensors");
}
```

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

```cpp
void set_state(const ov::SoPtr<ov::ITensor>& state) override 
{
  OPENVINO_ASSERT(state->get_shape() == 
                           m_state->get_shape(),
                              "Wrong tensor shape.");
  OPENVINO_ASSERT(state->get_element_type() == 
                           state->get_element_type(),                 // <=
                              "Wrong tensor type."   );
  OPENVINO_ASSERT(state->get_byte_size() == 
                           state->get_byte_size(),                    // <=
                              "Blob size of tensors are not equal.");
  std::memcpy(m_state->data(), state->data(), state->get_byte_size());
}
```

Предупреждения анализатора: 

1. V501 There are identical sub\-expressions 'state\-\>get\_element\_type\(\)' to the left and to the right of the '\=\=' operator\. [variable\_state\.hpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/template/src/variable_state.hpp) 23
1. V501 There are identical sub\-expressions 'state\-\>get\_byte\_size\(\)' to the left and to the right of the '\=\=' operator\. [variable\_state\.hpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/template/src/variable_state.hpp) 24

В первой строке сравниваются выражения _state\-\>get\_shape\(\)_ и _m\_state\-\>get\_shape\(\)_\. Однако в следующих строках из\-за copy\-paste сравниваются результаты вызова функций\-членов _get\_element\_type_ и _get\_byte\_size_ одного и того же объекта _state_\. Скорее всего, так получилось потому, что имена _m\_state_ и _state_ схожи меж собой, и программист не обратил на это внимания\.

Исправим код:

```cpp
....
OPENVINO_ASSERT(state->get_element_type() == 
                         m_state->get_element_type(),
                            "Wrong tensor type."     );
OPENVINO_ASSERT(state->get_byte_size() == 
                         m_state->get_byte_size(),
                            "Blob size of tensors are not equal.");
....
```

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

```cpp
void SubgraphExtractor::add_new_inputs(const std::vector<
                                                InputEdge>& new_inputs,
                                       const bool merge_inputs         ) 
{
  ....
  auto it = std::find_if(new_inputs.begin(), new_inputs.begin(),
                 [&](const InputEdge& input_edge) 
                 {
                   return get_input_tensor_name(m_onnx_graph, 
                                       input_edge) == input.first;
                 }                                                );
  ....
}
```

Предупреждение анализатора: 

[V539](https://pvs-studio.ru/ru/docs/warnings/v539/) \[CERT\-CTR53\-CPP\] Consider inspecting iterators which are being passed as arguments to function 'find\_if'\. [subgraph\_extraction\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/frontends/onnx/frontend/src/detail/subgraph_extraction.cpp) 300

Если мы обратим внимание на вызов функции _std::find\_if_, то заметим, что вторым аргументом должен быть вызов _new\_inputs\.end\(\)_\. В текущем состоянии код всегда будет возвращать _new\_inputs\.begin\(\)_\.

Исправим код:

```cpp
....
auto it = std::find_if(new_inputs.begin(), new_inputs.end(),
                       [&](const InputEdge& input_edge) 
                       {
                         return get_input_tensor_name(
                                   m_onnx_graph, input_edge) == input.first;
                       });
....
```

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

```cpp
std::vector<PortConfig> inConfs;
....
MemoryDescPtr Node::getBaseMemDescAtInputPort(size_t portNum) const 
{
  if (auto primDesc = getSelectedPrimitiveDescriptor()) 
  {
    const auto& inConfs = primDesc->getConfig().inConfs;
    if (inConfs.size() < portNum)                                // N1
    {                       
      OPENVINO_THROW("Can't get input memory desc at port: ",
                      portNum, ", incorrect port number"     );
    }
    return inConfs[portNum].getMemDesc();                        // N2
  }
  OPENVINO_THROW("Can't get input memory desc, 
                         primitive descriptor is not selected");
}
```

Предупреждение анализатора: 

[V557](https://pvs-studio.ru/ru/docs/warnings/v557/) \[CERT\-ARR30\-C\] Array overrun is possible\. The 'portNum' index is pointing beyond array bound\. [node\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/intel_cpu/src/node.cpp) 402

На первый взгляд, в этом фрагменте кода нет ничего не обычного, и кажется, что анализатор ругается зря\. Но нет\. Чтобы было проще объяснить, я отметил строки, на которые нужно обратить внимание\. 

В строке N1 в условии проверяется выражение _inConfs\.size\(\) < portNum_\. Условие становится _false_, когда _portNum <\= inConfs\.size\(\)_\. Затем в строке N2 происходит доступ к контейнеру _inConfs_\. Доступ к нему должен происходить по индексам в диапазоне \[0 \.\. N – 1\]\. Однако в граничном случае, когда _portNum \=\= inConfs\.size\(\)_, произойдёт выход за границу контейнера, что ведёт к неопределённому поведению\.

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

```cpp
if (portNum >= inConfs.size()) { .... }
```

Я также поменял операнды местами, поскольку, по моему скромному мнению, читается такая проверка человеком проще\.

Прочитав об этой ошибке, читатель может вызвать пояснительную бригаду: "В этом примере нет ни опечаток, ни проблем с copy\-paste, тогда что он здесь делает?" Дело в том, что эту ошибку размножили:

```cpp
....
std::vector<PortConfig> outConfs;
....
MemoryDescPtr Node::getBaseMemDescAtOutputPort(size_t portNum) const 
{
  if (auto primDesc = getSelectedPrimitiveDescriptor()) 
  {
    const auto& outConfs = primDesc->getConfig().outConfs;
    if (outConfs.size() < portNum)                               // <=
    {
      OPENVINO_THROW("Can't get output memory desc at port: ", 
                      portNum, ", incorrect port number"      );
    }
    return outConfs[portNum].getMemDesc();                       // <=
  }
  OPENVINO_THROW("Can't get output memory desc, 
                      primitive descriptor is not selected");
}
```

Предупреждение анализатора: 

[V557](https://pvs-studio.ru/ru/docs/warnings/v557/) \[CERT\-ARR30\-C\] Array overrun is possible\. The 'portNum' index is pointing beyond array bound\. [node\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/intel_cpu/src/node.cpp) 413

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

Поправим и её:

```cpp
if (portNum >= outConfs.size()) { .... }
```

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

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

А сейчас сыграем в мини\-игру: вам надо найти опечатки в коде проекта OpenVINO\. Кто нашёл, тот молодец\! Кто не нашёл, тоже молодцы\! Однако, надеюсь, всем стало очевидно — этот пример является одной большой причиной использовать статический анализатор\.

<details>
   <summary>Найдите здесь опечатки:</summary>

```cpp
template <class Key, class Value>
using caseless_unordered_map = std::unordered_map<Key, Value, 
                                   CaselessHash<Key>, CaselessEq<Key>>;
using TypeToNameMap = ov::intel_cpu::
                          caseless_unordered_map<std::string, Type>;

static const TypeToNameMap& get_type_to_name_tbl() {
    static const TypeToNameMap type_to_name_tbl = {
        {"Constant", Type::Input},
        {"Parameter", Type::Input},
        {"Result", Type::Output},
        {"Eye", Type::Eye},
        {"Convolution", Type::Convolution},
        {"GroupConvolution", Type::Convolution},
        {"MatMul", Type::MatMul},
        {"FullyConnected", Type::FullyConnected},
        {"MaxPool", Type::Pooling},
        {"AvgPool", Type::Pooling},
        {"AdaptiveMaxPool", Type::AdaptivePooling},
        {"AdaptiveAvgPool", Type::AdaptivePooling},
        {"Add", Type::Eltwise},
        {"IsFinite", Type::Eltwise},
        {"IsInf", Type::Eltwise},
        {"IsNaN", Type::Eltwise},
        {"Subtract", Type::Eltwise},
        {"Multiply", Type::Eltwise},
        {"Divide", Type::Eltwise},
        {"SquaredDifference", Type::Eltwise},
        {"Maximum", Type::Eltwise},
        {"Minimum", Type::Eltwise},
        {"Mod", Type::Eltwise},
        {"FloorMod", Type::Eltwise},
        {"Power", Type::Eltwise},
        {"PowerStatic", Type::Eltwise},
        {"Equal", Type::Eltwise},
        {"NotEqual", Type::Eltwise},
        {"Greater", Type::Eltwise},
        {"GreaterEqual", Type::Eltwise},
        {"Less", Type::Eltwise},
        {"LessEqual", Type::Eltwise},
        {"LogicalAnd", Type::Eltwise},
        {"LogicalOr", Type::Eltwise},
        {"LogicalXor", Type::Eltwise},
        {"LogicalNot", Type::Eltwise},
        {"Relu", Type::Eltwise},
        {"LeakyRelu", Type::Eltwise},
        {"Gelu", Type::Eltwise},
        {"Elu", Type::Eltwise},
        {"Tanh", Type::Eltwise},
        {"Sigmoid", Type::Eltwise},
        {"Abs", Type::Eltwise},
        {"Sqrt", Type::Eltwise},
        {"Clamp", Type::Eltwise},
        {"Exp", Type::Eltwise},
        {"SwishCPU", Type::Eltwise},
        {"HSwish", Type::Eltwise},
        {"Mish", Type::Eltwise},
        {"HSigmoid", Type::Eltwise},
        {"Round", Type::Eltwise},
        {"PRelu", Type::Eltwise},
        {"Erf", Type::Eltwise},
        {"SoftPlus", Type::Eltwise},
        {"SoftSign", Type::Eltwise},
        {"Select", Type::Eltwise},
        {"Log", Type::Eltwise},
        {"BitwiseAnd", Type::Eltwise},
        {"BitwiseNot", Type::Eltwise},
        {"BitwiseOr", Type::Eltwise},
        {"BitwiseXor", Type::Eltwise},
        {"Reshape", Type::Reshape},
        {"Squeeze", Type::Reshape},
        {"Unsqueeze", Type::Reshape},
        {"ShapeOf", Type::ShapeOf},
        {"NonZero", Type::NonZero},
        {"Softmax", Type::Softmax},
        {"Reorder", Type::Reorder},
        {"BatchToSpace", Type::BatchToSpace},
        {"SpaceToBatch", Type::SpaceToBatch},
        {"DepthToSpace", Type::DepthToSpace},
        {"SpaceToDepth", Type::SpaceToDepth},
        {"Roll", Type::Roll},
        {"LRN", Type::Lrn},
        {"Split", Type::Split},
        {"VariadicSplit", Type::Split},
        {"Concat", Type::Concatenation},
        {"ConvolutionBackpropData", Type::Deconvolution},
        {"GroupConvolutionBackpropData", Type::Deconvolution},
        {"StridedSlice", Type::StridedSlice},
        {"Slice", Type::StridedSlice},
        {"Tile", Type::Tile},
        {"ROIAlign", Type::ROIAlign},
        {"ROIPooling", Type::ROIPooling},
        {"PSROIPooling", Type::PSROIPooling},
        {"DeformablePSROIPooling", Type::PSROIPooling},
        {"Pad", Type::Pad},
        {"Transpose", Type::Transpose},
        {"LSTMCell", Type::RNNCell},
        {"GRUCell", Type::RNNCell},
        {"AUGRUCell", Type::RNNCell},
        {"RNNCell", Type::RNNCell},
        {"LSTMSequence", Type::RNNSeq},
        {"GRUSequence", Type::RNNSeq},
        {"AUGRUSequence", Type::RNNSeq},
        {"RNNSequence", Type::RNNSeq},
        {"FakeQuantize", Type::FakeQuantize},
        {"BinaryConvolution", Type::BinaryConvolution},
        {"DeformableConvolution", Type::DeformableConvolution},
        {"TensorIterator", Type::TensorIterator},
        {"Loop", Type::TensorIterator},
        {"ReadValue", Type::MemoryInput},  // for construction from name
                                           // ctor, arbitrary name is used
        {"Assign", Type::MemoryOutput},    // for construction from layer ctor
        {"Convert", Type::Convert},
        {"NV12toRGB", Type::ColorConvert},
        {"NV12toBGR", Type::ColorConvert},
        {"I420toRGB", Type::ColorConvert},
        {"I420toBGR", Type::ColorConvert},
        {"MVN", Type::MVN},
        {"NormalizeL2", Type::NormalizeL2},
        {"ScatterUpdate", Type::ScatterUpdate},
        {"ScatterElementsUpdate", Type::ScatterElementsUpdate},
        {"ScatterNDUpdate", Type::ScatterNDUpdate},
        {"Interpolate", Type::Interpolate},
        {"RandomUniform", Type::RandomUniform},
        {"ReduceL1", Type::Reduce},
        {"ReduceL2", Type::Reduce},
        {"ReduceLogicalAnd", Type::Reduce},
        {"ReduceLogicalOr", Type::Reduce},
        {"ReduceMax", Type::Reduce},
        {"ReduceMean", Type::Reduce},
        {"ReduceMin", Type::Reduce},
        {"ReduceProd", Type::Reduce},
        {"ReduceSum", Type::Reduce},  
        {"ReduceLogSum", Type::Reduce},
        {"ReduceLogSumExp", Type::Reduce},
        {"ReduceSumSquare", Type::Reduce},
        {"Broadcast", Type::Broadcast},  
        {"EmbeddingSegmentsSum", Type::EmbeddingSegmentsSum},
        {"EmbeddingBagPackedSum", Type::EmbeddingBagPackedSum},
        {"EmbeddingBagOffsetsSum", Type::EmbeddingBagOffsetsSum},
        {"Gather", Type::Gather},
        {"GatherElements", Type::GatherElements},
        {"GatherND", Type::GatherND},
        {"GridSample", Type::GridSample},
        {"OneHot", Type::OneHot},
        {"RegionYolo", Type::RegionYolo},
        {"ShuffleChannels", Type::ShuffleChannels},
        {"DFT", Type::DFT},
        {"IDFT", Type::DFT},
        {"RDFT", Type::RDFT},
        {"IRDFT", Type::RDFT},
        {"Abs", Type::Math},
        {"Acos", Type::Math},
        {"Acosh", Type::Math},  
        {"Asin", Type::Math},
        {"Asinh", Type::Math},
        {"Atan", Type::Math},
        {"Atanh", Type::Math},
        {"Ceil", Type::Math},
        {"Ceiling", Type::Math},
        {"Cos", Type::Math},
        {"Cosh", Type::Math},
        {"Floor", Type::Math},
        {"HardSigmoid", Type::Math},
        {"If", Type::If},
        {"Neg", Type::Math},
        {"Reciprocal", Type::Math},
        {"Selu", Type::Math},
        {"Sign", Type::Math},
        {"Sin", Type::Math},
        {"Sinh", Type::Math},
        {"SoftPlus", Type::Math},
        {"Softsign", Type::Math},
        {"Tan", Type::Math},
        {"CTCLoss", Type::CTCLoss},
        {"Bucketize", Type::Bucketize},
        {"CTCGreedyDecoder", Type::CTCGreedyDecoder},
        {"CTCGreedyDecoderSeqLen", Type::CTCGreedyDecoderSeqLen},
        {"CumSum", Type::CumSum},
        {"DetectionOutput", Type::DetectionOutput},
        {"ExperimentalDetectronDetectionOutput",
                      Type::ExperimentalDetectronDetectionOutput},
        {"LogSoftmax", Type::LogSoftmax},
        {"TopK", Type::TopK},
        {"GatherTree", Type::GatherTree},
        {"GRN", Type::GRN},
        {"Range", Type::Range},
        {"Proposal", Type::Proposal},
        {"ReorgYolo", Type::ReorgYolo},
        {"ReverseSequence", Type::ReverseSequence},
        {"ExperimentalDetectronTopKROIs", 
                      Type::ExperimentalDetectronTopKROIs},
        {"ExperimentalDetectronROIFeatureExtractor",
                      Type::ExperimentalDetectronROIFeatureExtractor},
        {"ExperimentalDetectronPriorGridGenerator",
                      Type::ExperimentalDetectronPriorGridGenerator},
        {"ExperimentalDetectronGenerateProposalsSingleImage",
                      Type::ExperimentalDetectronGenerateProposalsSingleImage},
        {"ExtractImagePatches", Type::ExtractImagePatches},
        {"GenerateProposals", Type::GenerateProposals},
        {"Inverse", Type::Inverse},
        {"NonMaxSuppression", Type::NonMaxSuppression},
        {"NonMaxSuppressionIEInternal", Type::NonMaxSuppression},
        {"NMSRotated", Type::NonMaxSuppression},
        {"MatrixNms", Type::MatrixNms},
        {"MulticlassNms", Type::MulticlassNms},
        {"MulticlassNmsIEInternal", Type::MulticlassNms},
        {"Multinomial", Type::Multinomial},
        {"Reference", Type::Reference},
        {"Subgraph", Type::Subgraph},
        {"PriorBox", Type::PriorBox},
        {"PriorBoxClustered", Type::PriorBoxClustered},
        {"Interaction", Type::Interaction},
        {"MHA", Type::MHA},
        {"Unique", Type::Unique},
        {"Ngram", Type::Ngram},
        {"ScaledDotProductAttention", Type::ScaledDotProductAttention},
        {"ScaledDotProductAttentionWithKVCache", 
                          Type::ScaledDotProductAttention},
        {"PagedAttentionExtension", Type::ScaledDotProductAttention},
        {"RoPE", Type::RoPE},
        {"GatherCompressed", Type::Gather},
        {"CausalMaskPreprocess", Type::CausalMaskPreprocess},
    };
    return type_to_name_tbl;
}
```


</details>
Забавно, что многие, увидев приведённый выше код, сочтут выполнение задачи по поиску ошибок в нём неразумной потерей времени\. Как результат, сразу пролистают сюда, дабы посмотреть ответ\. 

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

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

Предупреждения анализатора:

1. [V766](https://pvs-studio.ru/ru/docs/warnings/v766/) An item with the same key '"Abs"' has already been added\. [cpu\_types\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/intel_cpu/src/cpu_types.cpp) 178
1. [V766](https://pvs-studio.ru/ru/docs/warnings/v766/) An item with the same key '"SoftPlus"' has already been added\. [cpu\_types\.cpp](https://github.com/openvinotoolkit/openvino/blob/2d8ac08bf1f87f8ac455eae381213b52e781fe8c/src/plugins/intel_cpu/src/cpu_types.cpp) 198

Если кратко и по делу, то вот они — опечатки:

```cpp
static const TypeToNameMap& get_type_to_name_tbl() {
  static const TypeToNameMap type_to_name_tbl = {
    ....,
    {"Abs", Type::Eltwise},                   // <=
    ....,
    {"SoftPlus", Type::Eltwise},              // <=
    ....,
    {"Abs", Type::Math},                      // <=
    ....,
    {"SoftPlus", Type::Math},                 // <=
    ...., 
  };
  return type_to_name_tbl;
}
```

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

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

Вот такое вот интересное приключение по опечаткам у нас получилось\. 

Это была первая часть статьи про проверку кода проекта OpenVINO\. Желаю вам быть более аккуратными, и да прибудет с вами сила концентрации внимания\.

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

Хочется ещё раз напомнить: найденные опечатки и связанные с ними ошибки говорят лишь о том, что программисты тоже люди\. А для счастья, \(и исправления ошибок в коде\) им всего лишь нужен хотя и маленький, но эффективный и свой — статический анализатор кода\.

И, как у нас уже исторически сложилось, предлагаю [попробовать](https://pvs-studio.ru/ru/pvs-studio/try-free/) наш анализатор PVS\-Studio\. Для Open Source проектов у нас [предоставляется](https://pvs-studio.ru/ru/order/open-source-license/) бесплатная лицензия\.

Берегите себя и всего доброго\!