В анализаторе PVS-Studio существует диагностика V595 "The pointer was utilized before it was verified against nullptr". Я получаю много вопросов от наших пользователей, которые касаются как раз этой диагностики. Поэтому я решил заранее подготовить обстоятельный ответ, который поможет мне в дальнейшем объяснять, как работает эта диагностика.
Описание диагностики V595 в документации: The pointer was utilized before it was verified against nullptr.
Примечание. С момента написания статьи в 2015 году многое изменилось, и диагностика V595 стала более интеллектуальной. Теперь анализатор умеет заглядывать в тела функций, в том числе расположенных в разных файлах. Благодаря этому могут быть обнаружены более сложные варианты ошибок. См. раздел "Технология статического анализа кода PVS-Studio".
Типовой вопрос, касающийся V595, звучит следующим образом:
У меня есть код следующего вида:
void MyClass::Do()
{
m_ptr->Foo(1, 2, 3);
Process(1, 2, 3, 4, 5);
}
Член класса 'm_ptr' иногда может принимать нулевые значения. В результате, происходит падание программы. Я ожидал, что анализатор PVS-Studio выдаст предупреждение, что указатель 'm_ptr' следует проверить перед использование. Я хочу увидеть предупреждение V595, а его нет. Прошу пояснить эту ситуацию.
Постараюсь дать подробный ответ.
Анализатор PVS-Studio в общем случае не диагностирует ситуацию, что указатель может быть нулевой и перед использованием его следует проверить.
Если сделать "тупую" диагностику, которая будет говорить, что указатель используется без проверки, то этой диагностикой будет невозможно пользоваться. Будет такое невероятное количество ложных срабатываний, что даже если и будет найдена ошибка, она потонет в ложных предупреждениях и её никто не найдёт. Поэтому такой вариант диагностики реализовывать нет смысла.
Идеологически правильно пытаться понять, может ли указатель принимать нулевое значение. Это невероятно сложная техническая задача. Нужно анализировать call graph и пытаться понять возможные значения переменных. На практике, это невозможно. Различные анализаторы, и PVS-Studio в их числе, пытаются частично решать эту задачу для простых случаев. Но в целом все равно получается плохо. Многие ошибки не будут замечены, многие пропущены.
Например, анализатор PVS-Studio может найти такую ошибку только для простых случаев. Пример:
void Foo(int *p)
{
if (!p)
{
p[1] = 2; //V522
}
}
Анализатор видит, что мы входим в тело if, если указатель равен 0. Значит разыменование указателя приведёт к ошибке. Это очень простой пример. В сложных вариантах, например, как в первом примере, анализатор бессилен. Он не может вычислить, что лежит в 'm_ptr' в данный момент.
Поскольку, как мы видим анализатор, слаб в решении подобных задач напрямую, существуют обходные пути поиска ошибок. Одним из таких обходных способов является диагностика V595. Принцип её работы: надо выдавать предупреждение, если указатель в начале разыменовывается, а потом проверяется.
Поясню на примере. Здесь PVS-Studio не знает, что будет в 'p' и поэтому ему нечего сказать:
void Foo()
{
int *p = Get();
p[0] = 1;
....
}
Однако в один из моментов, программист вспомнил, что указатель может быть равен нулю и ниже имеется проверка указателя. Т.е. мы имеем вот такой код:
void Foo()
{
int *p = Get();
p[0] = 1; // V595
....
if (p == NULL)
Zzz();
}
PVS-Studio выдаёт предупреждение V595. Анализатор не способен вычислить, что вернёт функция Get(). Но ему это и не нужно. Он видит, что ниже указатель проверятся на равенство нулю. Это значит, что возможны ситуации, когда указатель нулевой. И разыменовывать его без проверки нельзя.
Надеюсь я пояснил как работает анализатор и почему анализатор не выдаёт предупреждение для кода показанного в самом начале. Ниже нет проверки, что переменная 'm_ptr' равна 0, поэтому нет и предупреждения. К сожалению, анализатор здесь бессилен.
0