# V595. Pointer was used before its check for nullptr. Check lines: N1, N2.

Анализатор обнаружил потенциальную ошибку, которая может привести к разыменовыванию нулевого указателя.

Анализатор заметил в коде следующую ситуацию. В начале, указатель используется. А уже затем этот указатель проверяется на значение NULL. Это может означать одно из двух:

1) Возникнет ошибка, если указатель будет равен NULL.

2) Программа всегда работает корректно, так как указатель всегда не равен NULL. Проверка является лишней.

Рассмотрим первый вариант. Ошибка есть.

    buf = Foo();
    pos = buf->pos;
    if (!buf) return -1;

Если указатель 'buf' окажется равен NULL, то выражение 'buf->pos ' приведёт к ошибке. Анализатор выдаст предупреждение на этот код, указав 2 строки. Первая строка - это то место, где используется указатель. Вторая строка - это то место, где указатель сравнивается со значением NULL.

Исправленный вариант кода:

    buf = Foo();
    if (!buf) return -1;
    pos = buf->pos;

Рассмотрим второй вариант. Ошибки нет.

    void F(MyClass *p)
    {
      if (!IsOkPtr(p))
        return;
      printf("%s", p->Foo());
      if (p) p->Clear();
    }

Этот код всегда работает корректно. Указатель всегда не равен NULL. Однако анализатор не разобрался в этой ситуации и выдал предупреждение. Чтобы оно исчезло, следует удалить проверку "if (p)". Она не имеет практического смысла и только может запутать программиста, читающего код.

Исправленный вариант:

    void F(MyClass *p)
    {
      if (!IsOkPtr(p))
        return;
      printf("%s", p->Foo());
      p->Clear();
    }

В случае если анализатор ошибается, то кроме изменения кода, можно использовать комментарий для подавления предупреждений. Пример: "p->Foo(); //-V595".

Примечание N1.

Некоторые пользователи сообщают, что анализатор выдает предупреждение V595 на корректный код, Пример:

    static int Foo(int *dst, int *src)
    {
      *dst = *src; // V595 !
      if (src == 0)
        return 0;
      return Foo(dst, src);
    }
    ...
    int a = 1, b = 2;
    int c = Foo(&a, &b);

Да, здесь анализатор выдает ложное срабатывание. Код корректен и указатель 'src' не может быть равен NULL в тот момент, когда выполняется присваивание "*dst = *src". Возможно, в дальнейшем мы реализуем исключение для подобных случаев, но пока не спешим это делать. Хотя здесь нет ошибки, анализатор выявил избыточность кода. Функцию можно сократить. При этом пропадет предупреждение V595, а код станет проще.

Улучшенный вариант:

    int Foo(int *dst, int *src)
    {
      assert(dst && src);
      *dst = *src;
      return Foo(dst, src);
    }

Примечание N2.

Иногда в программах можно встретить код следующего вида:

    int *x=&p->m_x;   //V595
    if (p==NULL) return(OV_EINVAL);

Вычисляется указатель на член класса. Этот указатель не разыменовывается и кажется, что анализатор зря выдаёт диагностическое сообщение V595. Однако, этот код приводит к неопределённому поведению. Если программа работает, это везение и не более того. Нельзя вычислять выражение, если "&p->m_x", если указатель 'p' равен нулю.

С аналогичной ситуацией программист может встретиться, желая отсортировать массив:

    int array[10];
    std::sort(&array[0], &array[10]); // Undefined behavior

Использование &array[10] приводит к неопределенному поведению, так как элемент array[10] уже лежит за границами массива. Но использование арифметики указателей вполне допустимо. Можно обратиться к указателю, который адресует конечный элемент массива. Поэтому, необходимо написать следующий корректный код:

    int array[10];
    std::sort(array, array+10); //ok

## Дополнительные материалы

- Андрей Карпов. Пояснение про диагностику V595.

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки разыменования нулевого указателя/нулевой ссылки [* см. примечание касательно языков C#, Java].

Данная диагностика классифицируется как:

CWE-476 CERT-EXP34-C

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V595.