﻿# Сколько UB в моём компиляторе?

У C и C\+\+ программистов две головные боли в плане ошибок: утечки памяти и неопределённое поведение\. И как вы догадались из названия, речь пойдёт о неопределённом поведении\. И каком\-то "моём" компиляторе\. Если точнее, то о наборе компиляторов и инструментах для их разработки, а именно LLVM\. Почему "моём"? Потому что мы очень любим Clang, входящий в состав LLVM, и пользуемся им на постоянной основе\.

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

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

**Предисловие**

Неопределённое поведение или Undefined Behavior \(UB\) — это когда программист написал код, который позволяют написать правила языка, и сделал это вроде бы правильно, но при этом программа работает неправильно\. А может и правильно\. Ведь UB — это когда стандарт вообще ничего не гарантирует по твоему коду\.

Результат выполнения такой программы будет зависеть только от компилятора и целевой платформы\. Из этого делаем вывод, что компиляторы могут сделать с вашей программой всё что угодно\. Порой то, что вы и хотели\. Но при этом оказывается, что ошибка просто умело скрывается, и вам повезло\. 

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

Например, когда на вашей стороне тесты пройдены, и продукт уже у клиента, но у него что\-то не работает, хотя, казалось бы, работало пять минут назад у вас\. И, конечно же, логов или понятных объяснений этому нет\. Вернее, объяснение есть — это UB, но дойти до этого нужно трудным путём отладки\.

Или возможен другой сценарий: ты обновляешь компилятор, собираешь им программу, и всё начинает ломаться\. Все тесты красные\. Жуть\. И всё из\-за пары строк кода, которые компилятор теперь стал использовать против вас\. 

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

**Путеводитель C\+\+ программиста по неопределённому поведению**

Кстати, мы начали публиковать на нашем сайте книгу Дмитрия Свиридкина под редакцией Андрея Карпова про неопределённое поведение\. [Ссылка](https://pvs-studio.ru/ru/blog/posts/cpp/1129/) на первую часть\. Заодно приглашаю подписаться на [ежемесячный дайждест](https://pvs-studio.ru/ru/subscribe/), чтобы не пропустить другие части книги и остальные интересные материалы\.

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

А начнём мы с бессмертной классики в мире ошибок\. Разыменование нулевого указателя\. 

```cpp
void LineTable::Dump(Stream *s, Target *target, Address::DumpStyle style,
                     Address::DumpStyle fallback_style, bool show_line_ranges) 
{
  const size_t count = m_entries.size();
  LineEntry line_entry;
  SupportFileSP prev_file;   // <=
  for (size_t idx = 0; idx < count; ++idx) {
    ConvertEntryAtIndexToLineEntry(idx, line_entry);
    line_entry.Dump(s, target, *prev_file != *line_entry.original_file_sp, // <=
                    style, fallback_style, show_line_ranges);
    s->EOL();
    prev_file = line_entry.original_file_sp;
  }
}
```

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

[V522](https://pvs-studio.ru/ru/docs/warnings/v522/) Dereferencing of the null pointer 'prev\_file' might take place\. LineTable\.cpp [363](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/lldb/source/Symbol/LineTable.cpp#L363)

Как мы видим, анализатор указывает на переменную _prev\_file_\. А вот что представляет из себя эта переменная, точнее её тип:

```cpp
typedef std::shared_ptr<lldb_private::SupportFile> SupportFileSP;
```

При таком объявлении _std::shared\_ptr_ [инициализируется нулём](https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr#:~:text=1%2C2),empty%20shared_ptr.)\. Ну а [разыменование нулевого указателя](https://pvs-studio.ru/ru/blog/posts/cpp/0306/) приводит к UB\.

Для удобства заведём счётчик, который будет считать количество UB\.

Счётчик UB: 0 —\> 1\.

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

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

```cpp
bool Sema::checkStringLiteralArgumentAttr(const AttributeCommonInfo &CI,
                                          const Expr *E, StringRef &Str,
                                          SourceLocation *ArgLocation) 
{
  const auto *Literal = dyn_cast<StringLiteral>(E->IgnoreParenCasts());
  ....
}
```

Как можно заметить, указатель _E_ разыменовывается в первой строке без проверки\. А дальше произошло ЭТО\.

<details>
   <summary>Не для слабонервных</summary>

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

* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: [349](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/clang/lib/Sema/SemaDeclAttr.cpp#L349), [1801](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/clang/lib/Sema/SemaDeclAttr.cpp#L1801)\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: [349](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/clang/lib/Sema/SemaDeclAttr.cpp#L349), [1974](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/clang/lib/Sema/SemaDeclAttr.cpp#L1974)\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 1984\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 1999\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 2046\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 2381\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 3188\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 3355\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 3376\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 3423\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 3529\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 3543\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 4328\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 5416\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 6353\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 6437\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 6447\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 6965\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 7096\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 7239\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 7467\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 7742\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 7772\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 7825\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 7842\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 7872\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 8224\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 8305\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 8455\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 8602\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 8819\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 8827\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 8870\. SemaDeclAttr\.cpp
* V522 Dereferencing of the null pointer 'E' might take place\. The null pointer is passed into 'checkStringLiteralArgumentAttr' function\. Inspect the second argument\. Check lines: 349, 977\.  SemaDeclAttr\.cpp




</details>


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

Первое:

```cpp
static void handleAssumumptionAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// Handle the case where the attribute has a text message.
StringRef Str;
SourceLocation AttrStrLoc;
if (!S.checkStringLiteralArgumentAttr(AL, 0, Str, &AttrStrLoc))
  return;
....
}
```

Второе:

```cpp
static void handleWeakRefAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
....
if (AL.getNumArgs() && S.checkStringLiteralArgumentAttr(AL, 0, Str))
....
}
```

Как мы видим, в обоих случаях вторым параметром передаётся 0\.

> "UB может убить вашего котёнка, даже если у вас его нет"\.

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

Помните про счётчик UB? Так вот: 1 —\> 36\.

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

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

```cpp
struct ForceCodegenLinking {
    ForceCodegenLinking() {
      // We must reference the passes in such a way that compilers will not
      // delete it all as dead code, even with whole program optimization,
      // yet is effectively a NO-OP. As the compiler isn't smart enough
      // to know that getenv() never returns -1, this will do the job.
      // This is so that globals in the translation units where these functions
      // are defined are forced to be initialized, populating various
      // registries.
      if (std::getenv("bar") != (char*) -1)
        return;

      (void) llvm::createFastRegisterAllocator();
      (void) llvm::createBasicRegisterAllocator();
      (void) llvm::createGreedyRegisterAllocator();
      (void) llvm::createDefaultPBQPRegisterAllocator();

      (void)llvm::createBURRListDAGScheduler(nullptr,
                                             llvm::CodeGenOptLevel::Default);
      (void)llvm::createSourceListDAGScheduler(nullptr,
                                               llvm::CodeGenOptLevel::Default);
      (void)llvm::createHybridListDAGScheduler(nullptr,
                                               llvm::CodeGenOptLevel::Default);
      (void)llvm::createFastDAGScheduler(nullptr,
                                         llvm::CodeGenOptLevel::Default);
      (void)llvm::createDefaultScheduler(nullptr,
                                         llvm::CodeGenOptLevel::Default);
      (void)llvm::createVLIWDAGScheduler(nullptr,
                                         llvm::CodeGenOptLevel::Default);
    }
  } ForceCodegenLinking; // Force link by creating a global definition.
}
```

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

```cpp
ScheduleDAGSDNodes *llvm::createBURRListDAGScheduler(SelectionDAGISel *IS,
                                                    CodeGenOptLevel OptLevel)
{
  const TargetSubtargetInfo &STI = IS->MF->getSubtarget();
  ....
}
```

Или

```cpp
ScheduleDAGSDNodes* createDefaultScheduler(SelectionDAGISel *IS,
                                           CodeGenOpt::Level OptLevel) 
{
  const TargetLowering *TLI = IS->TLI;
  const TargetSubtargetInfo &ST = IS->MF->getSubtarget();
  ....
}
```

Если мы посмотрим на вызов функции в конструкторе, то увидим, что в качестве первого аргумента ей передаётся _nullptr_\. А это как раз первый параметр _IS_, который в первой же строке функции разыменовывается\.

Странный код\. Возможно, разработчики LLVM действительно что\-то знают и научились управлять UB\. А может, они просто не любят котят :D

В каждой такой функции с нулевым первым аргументом происходит его разыменование\.

Соответственно, срабатывания анализатора:

* V522 Dereferencing of the null pointer might take place\. The null pointer is passed into 'createBURRListDAGScheduler' function\. Inspect the first argument\. Check lines: '[ScheduleDAGRRList\.cpp:3147](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGRRList.cpp#L3147)', '[LinkAllCodegenComponents\.h:40](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/llvm/include/llvm/CodeGen/LinkAllCodegenComponents.h#L40)'\. 
* V522 Dereferencing of the null pointer might take place\. The null pointer is passed into 'createSourceListDAGScheduler' function\. Inspect the first argument\. Check lines: '[ScheduleDAGRRList\.cpp:3161](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGRRList.cpp#L3161)', '[LinkAllCodegenComponents\.h:42](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/llvm/include/llvm/CodeGen/LinkAllCodegenComponents.h#L42)'\. 
* V522 Dereferencing of the null pointer might take place\. The null pointer is passed into 'createHybridListDAGScheduler' function\. Inspect the first argument\. Check lines: '[ScheduleDAGRRList\.cpp:3175](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGRRList.cpp#L3175)', '[LinkAllCodegenComponents\.h:44](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/llvm/include/llvm/CodeGen/LinkAllCodegenComponents.h#L44)'\. 
* \.\.\. \(остальные три аналогичные\)

Счётчик UB: 36 —\> 42\.

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

Очередной фрагмент, очередное UB:

```cpp
Value *CodeGenFunction::EmitX86BuiltinExpr(unsigned BuiltinID,
                                           const CallExpr *E) 
{
  ....
  unsigned SrcNumElts =
        cast<llvm::FixedVectorType>(Ops[1]->getType())->getNumElements();
  ....
  int Indices[16];
    for (unsigned i = 0; i != DstNumElts; ++i)
      Indices[i] = (i >= SrcNumElts) ? SrcNumElts + (i % SrcNumElts) : i;
  ....
}
```

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

[V609](https://pvs-studio.ru/ru/docs/warnings/v609/) Mod by zero\. Denominator 'SrcNumElts' \=\= 0\. [CGBuiltin\.cpp:14833](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/clang/lib/CodeGen/CGBuiltin.cpp#L14833)

Анализатор говорит обратить внимание на _SrcNumElts_\. Что ж, давайте разбираться\.

Как мы видим, в цикле используется тернарный оператор\. Условие проверки: _i_ больше или равен _SrcNumElts_\. То есть всегда, когда _SrcNumElts \=\= 0 _\(а проверок выше на это нет, и [_getNumElements_](https://github.com/llvm/llvm-project/blob/279a659e9772e48d95ad7d81f6deb00ee31e35e1/llvm/include/llvm/IR/DerivedTypes.h#L582C3-L582C46) может возвращать 0\), у нас выполняется _SrcNumElts \+ \(i % SrcNumElts\)_\. Как известно, при [делении на 0](https://pvs-studio.ru/ru/blog/terms/0085/) \(в том числе по модулю\) поведение [не определено](https://en.cppreference.com/w/cpp/language/operator_arithmetic#Built-in_multiplicative_operators:~:text=The%20result%20of%20built%2Din%20division%20is%20lhs%20divided%20by%20rhs.%20If%20rhs%20is%20zero%2C%20the%20behavior%20is%20undefined.)\.

Счётчик UB: 42 —\> 43\.

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

Довольно небольшая функция, состоящая только из _if_\-конструкций, и справедливое срабатывание анализатора:

```cpp
static bool StopAtComponentPre(const Symbol &component) {
  if constexpr (componentKind == ComponentKind::Ordered) {
    // Parent components need to be iterated upon after their
    // sub-components in structure constructor analysis.
    return !component.test(Symbol::Flag::ParentComp);
  } else if constexpr (componentKind == ComponentKind::Direct) {
    return true;
  } else if constexpr (componentKind == ComponentKind::Ultimate) {
    return component.has<ProcEntityDetails>() ||
        IsAllocatableOrObjectPointer(&component) ||
        (component.has<ObjectEntityDetails>() &&
            component.get<ObjectEntityDetails>().type() &&
            component.get<ObjectEntityDetails>().type()->AsIntrinsic());
  } else if constexpr (componentKind == ComponentKind::Potential) {
    return !IsPointer(component);
  } else if constexpr (componentKind == ComponentKind::PotentialAndPointer) {
    return true;
  }
}
```

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

[V591](https://pvs-studio.ru/ru/docs/warnings/v591/) Non\-void function should return a value\. [tools\.cpp:1278](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/flang/lib/Semantics/tools.cpp#L1278)

Как мы видим, в функции нет _return_ на случай, если все условия окажутся ложными\. Такое может произойти, так как enum class _ComponentKind_ содержит ещё одно значение _Scope_, не представленное здесь\. В таком случае поведение [не определено](https://en.cppreference.com/w/cpp/language/return#:~:text=Flowing%20off%20the,is%20undefined%20behavior.)\.

Причём неопределённое поведение не обязательно означает, что функция вернёт случайное значение \(_true_ или _false_\)\. [Это именно всё что угодно](https://pvs-studio.ru/ru/blog/posts/cpp/0917/)\.

Счётчик UB: 43 —\> 44\.

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

Вот этот фрагмент может показаться безопасным:

```cpp
bool AppleObjCRuntimeV2::NonPointerISACache::EvaluateNonPointerISA(
    ObjCISA isa, ObjCISA &ret_isa) {
  ....
  if (index > m_indexed_isa_cache.size())
    return false;

  LLDB_LOGF(log, "AOCRT::NPI Evaluate(ret_isa = 0x%" PRIx64 ")",
          (uint64_t)m_indexed_isa_cache[index]);
  ....
}
```

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

Но всё же ошибка тут имеется\. Если звёзды сойдутся таким образом, что переменная _index_ будет равна _m\_indexed\_isa\_cache\.size\(\)_, то случится оно\. Да\-да, [выход за границу массива](https://pvs-studio.ru/ru/blog/terms/0071/) и, как следствие, неопределённое поведение\.

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

[V557](https://pvs-studio.ru/ru/docs/warnings/v557/) Array overrun is possible\. The 'index' index is pointing beyond array bound\. AppleObjCRuntimeV2\.cpp [3308](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp#L3308)

Для исправления достаточно написать вот так:

```cpp
if (index >= m_indexed_isa_cache.size())
    return false;
```

И абсолютно такое же срабатывание, но чуть ниже по коду:

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

[V557](https://pvs-studio.ru/ru/docs/warnings/v557/) Array overrun is possible\. The 'index' index is pointing beyond array bound\. AppleObjCRuntimeV2\.cpp [3311](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp#L3311)

Счётчик UB: 44 —\> 46\.

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

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

```cpp
lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
    StdlibCoroutineHandleSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
    : SyntheticChildrenFrontEnd(*valobj_sp) {
  if (valobj_sp)
    Update();
}
```

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

[V664](https://pvs-studio.ru/ru/docs/warnings/v664/) The 'valobj\_sp' pointer is being dereferenced on the initialization list before it is verified against null inside the body of the constructor function\. Check lines: [99](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp#L99), 100\. Coroutines\.cpp

Счётчик UB: 46 —\> 47\.

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

В последнем фрагменте вернёмся к тому, с чего всё начиналось — к разыменованию нулевого указателя\. Уже, правда, в другом месте\.

```cpp
void SetInsertPoint(Instruction *I) {
  BB = I->getParent();
  InsertPt = I->getIterator();
  assert(InsertPt != BB->end() && "Can't read debug loc from end()");
  SetCurrentDebugLocation(I->getStableDebugLoc());
}
```

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

[V522](https://pvs-studio.ru/ru/docs/warnings/v522/) Dereferencing of the null pointer 'I' might take place\. The null pointer is passed into 'SetInsertPoint' function\. Inspect the first argument\. Check lines: '[IRBuilder\.h:188](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/llvm/include/llvm/IR/IRBuilder.h#L188)', '[OMPIRBuilder\.cpp:5983](https://github.com/llvm/llvm-project/blob/461274b81d8641eab64d494accddc81d7db8a09e/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp#L5983)'\.

Анализатор советует обратить внимание на параметр _I_\. Что ж, последуем его совету и посмотрим место вызова этой функции:

```cpp
if (UnreachableInst *ExitTI =
        dyn_cast<UnreachableInst>(ExitBB->getTerminator())) {
  CurBBTI->eraseFromParent();
  Builder.SetInsertPoint(ExitBB);
} else {
  Builder.SetInsertPoint(ExitTI);
}
```

Ещё одно странное место\. Судя по названию указателей \(_ExitTI_ и _ExitBB_\) и функций, которые через них вызываются \(_getTerminator_\), у меня сложилось впечатление, что их специально разыменовывают нулевыми, чтобы аварийно завершить выполнение программы :D \(хотя на самом деле результат будет неопределённым\)\.

Смотрите, мы получаем нашего "терминатора" _ExitTI_ из _ExitBB\-\>getTerminator\(\),_ предварительно кастанув его к _UnreachableInst_\. Всё это происходит в условии конструкции _if_\. Если он равен нулю, то выполняется _else_\-ветвь, где он передаётся в качестве аргумента функции и тут же разыменовывается\. Аста ла виста, бейби\.

Счётчик UB: 47 —\> 48\.

**Послесловие про разыменование нулевого указателя**

Тема разыменования нулевого указателя так же глубока, как кроличья нора\. Или ещё глубже\. Предлагаем развлечь себя вот таким замечательным докладом про \*\(char\*\)0 \= 0;

<https://www.youtube.com/watch?v=dFIqNZ8VbRY&ab_channel=cpponsea>

Если захочется ещё чего\-то такого интересного, то предлагаем заглянуть в "[Подборка крутых докладов по С\+\+ за 2023 год](https://pvs-studio.ru/ru/blog/posts/1120/)"\.

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

Счётчик UB показывает 48, а статья подходит к концу\.

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

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

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