Некоторые виды программных ошибок невозможно искать статическим анализом "в общем виде", или же это крайне затруднено. В этом случае рационально научиться выявлять множество отдельных паттернов ошибок этого вида. Или, другими словами, искать модельные варианты ошибок. Проще всего будет это понять на примерах.
В языке C и C++ есть ошибки, связанные с наличием в коде неопределённого поведения (Undefined Behavior, UB). Соответственно, статические анализаторы должны выявлять код, приводящий к UB. Вот только это легко сказать, но сложно сделать.
Неопределённое поведение — это не конкретный вид бага или опечатки. В стандартах C и C++ описано большое количество разнообразнейших ситуаций, приводящих к UB. Неопределённому поведению посвящено большое количество статей и книг. Более того, с выходом новых стандартов языков C и C++, появляются новые языковые конструкции, неверное использование которых приводит к неопределённому поведению новыми способами.
Поэтому на практике выявление неопределённого поведения сводится к созданию множества отдельных диагностик, каждая из которых ищет свои паттерны (модельные варианты) ошибок. Например:
Рассмотрим ещё один пример — разыменование нулевого указателя. Кстати, это тоже частный случай неопределённого поведения, но все рассматривают этот тип ошибок отдельно.
Анализатор может находить множество таких ошибок, выполняя анализ потока данных и управления. Простейший, но при этом реальный пример:
ViewProviderPage* vpp = getViewProviderPage(dView);
if (!vpp) {
return vpp->getQGVPage();
PVS-Studio предупреждает (V522), что если мы войдём внутрь оператора if, то произойдёт разыменование нулевого указателя.
К сожалению, в силу вычислительной сложности статические анализаторы не могут в общем случае отследить все пути выполнения программы и передачи данных. Поэтому анализаторы часто не знают, может указатель являться нулевым или нет.
Предупреждать на всякий случай об использовании всех указателей, про которые нет информации — плохая идея. Появится слишком много ложных срабатываний и анализатор невозможно будет использовать. Полезные срабатывания потонут в бесконечном потоке бесполезных, и программист перестанет их просматривать. Поэтому в PVS-Studio мы придерживаемся определённой философии: предупреждать только в том случае, когда есть повод подозревать наличие ошибки.
Можно ли что-то сделать, если даже анализатор не смог вычислить, может указатель быть нулевым или нет? Да, можно. Например, в PVS-Studio реализован вот такой модельный вариант поиска ошибок:
Ms::Segment* startSegment = score()->selection().startSegment();
startSegment->measure()->firstEnabled(); // <= (1)
if (!startSegment) { // <= (2)
return nullptr;
}
Предположим, анализатор в этом случае не смог вычислить, может ли указатель startSegment быть нулевым или нет. Однако косвенно можно с большой долей достоверности утверждать, что код всё-таки содержит ошибку.
Обратите внимание, указатель в начале разыменовывается (1), а затем проверяется (2). Раз указатель проверяют, значит он может быть равен nullptr. Следовательно, выше может произойти разыменование нулевого указателя.
Анализатор PVS-Studio распознаёт такой ошибочный паттерн и выдаёт предупреждение V595. Кстати, это очень распространённый антипаттерн, и мы уже собрали большую коллекцию таких багов.
Это очень показательный пример. Теоретически найти использование всех нулевых указателей можно с помощью анализа потока данных. Однако на практике анализаторы упираются в вычислительную сложность этой задачи. На помощь приходит поиск модельных вариантов ошибок, один из которых мы только что рассмотрели. Есть и другие способы, например, диагностика V757. Дополняя другу друга, диагностики хорошо справляются с общей задачей поиска нулевых указателей.
ГОСТ Р 71207-2024 — Статический анализ программного обеспечения. В разделе терминов дано следующее определение модельного варианта (ошибки):
Подтип некоторого типа ошибки в программе, отнесение к которому осуществляется исходя из особенностей реализации ошибки данного типа и особенностей языка программирования.
Примечание — Разбиение типа ошибки на модельные варианты применяется из-за технологических ограничений статического анализа в целях эффективной работы статических анализаторов, сводя задачу поиска ошибки более общего типа к задаче поиска ошибок множества подтипов.
Дополнительные ссылки:
0