﻿# Модельный вариант \(ошибки\)

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

В языке C и C\+\+ есть ошибки, связанные с наличием в коде [неопределённого поведения](https://pvs-studio.ru/ru/blog/terms/0066/) \(Undefined Behavior, UB\)\. Соответственно, статические анализаторы должны выявлять код, приводящий к UB\. Вот только это легко сказать, но сложно сделать\.

Неопределённое поведение — это не конкретный вид бага или опечатки\. В стандартах C и C\+\+ описано большое количество **разнообразнейших** ситуаций, приводящих к UB\. Неопределённому поведению посвящено большое количество статей и книг\. Более того, с выходом новых стандартов языков C и C\+\+, появляются новые языковые конструкции, неверное использование которых приводит к неопределённому поведению новыми способами\.

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

* аргументом оператора _delete_ является указатель типа _void \*_ \([V772](https://pvs-studio.ru/ru/docs/warnings/v772/)\);
* объявлен макрос, имя которого перекрывает ключевое слово или зарезервированный стандартом идентификатор \([V1059](https://pvs-studio.ru/ru/docs/warnings/v1059/)\);
* некорректные операции сдвига \([V610](https://pvs-studio.ru/ru/docs/warnings/v610/)\);
* и так далее\.

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

Анализатор может находить множество таких ошибок, выполняя анализ потока данных и управления\. Простейший, но при этом [реальный](https://pvs-studio.ru/ru/blog/posts/cpp/1072/) пример:

```cpp
ViewProviderPage* vpp = getViewProviderPage(dView);
if (!vpp) {
  return vpp->getQGVPage();
```

PVS\-Studio предупреждает \([V522](https://pvs-studio.ru/ru/docs/warnings/v522/)\), что если мы войдём внутрь оператора _if_, то произойдёт разыменование нулевого указателя\.

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

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

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

```cpp
Ms::Segment* startSegment = score()->selection().startSegment();

startSegment->measure()->firstEnabled();  // <= (1)

if (!startSegment) {                      // <= (2)
  return nullptr;
}
```

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

Обратите внимание, указатель в начале разыменовывается \(1\), а затем проверяется \(2\)\. Раз указатель проверяют, значит он может быть равен _nullptr_\. Следовательно, выше может произойти разыменование нулевого указателя\.

Анализатор PVS\-Studio распознаёт такой ошибочный паттерн и выдаёт предупреждение [V595](https://pvs-studio.ru/ru/docs/warnings/v595/)\. Кстати, это очень распространённый антипаттерн, и мы уже собрали большую [коллекцию](https://pvs-studio.ru/ru/blog/examples/v595/) таких багов\. 

Это очень показательный пример\. Теоретически найти использование всех нулевых указателей можно с помощью анализа потока данных\. Однако на практике анализаторы упираются в вычислительную сложность этой задачи\. На помощь приходит поиск модельных вариантов ошибок, один из которых мы только что рассмотрели\. Есть и другие способы, например, диагностика [V757](https://pvs-studio.ru/ru/docs/warnings/v757/)\. Дополняя другу друга, диагностики хорошо справляются с общей задачей поиска нулевых указателей\.

<details>
   <summary>Определение модельного варианта ошибки по ГОСТ Р 71207\\\-2024\\\.</summary>

[ГОСТ Р 71207\-2024](https://pvs-studio.ru/ru/pvs-studio/gost-71207/) — Статический анализ программного обеспечения\. В разделе терминов дано следующее определение модельного варианта \(ошибки\):

Подтип некоторого типа ошибки в программе, отнесение к которому осуществляется исходя из особенностей реализации ошибки данного типа и особенностей языка программирования\.

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


</details>
**Дополнительные ссылки:**

1. [Статический анализ кода](https://pvs-studio.ru/ru/blog/terms/0046/)\.
1. Андрей Карпов\. [Как работает статический анализ?](https://pvs-studio.ru/ru/blog/posts/1048/)
1. Андрей Карпов\. [Разыменовывание нулевого указателя приводит к неопределённому поведению](https://pvs-studio.ru/ru/blog/posts/cpp/0306/)\.