Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
>
>
>
V704. The expression is always false on…
menu mobile close menu
Проверка проектов
Сообщения PVS-Studio
Диагностики общего назначения (General Analysis, C++)
Диагностики общего назначения (General Analysis, C#)
Диагностики общего назначения (General Analysis, Java)
Микрооптимизации (C++)
Диагностика 64-битных ошибок (Viva64, C++)
Реализовано по запросам пользователей (C++)
Cтандарт MISRA
Стандарт AUTOSAR
Стандарт OWASP (C++)
Стандарт OWASP (C#)
Проблемы при работе анализатора кода
Дополнительная информация
toggle menu Оглавление

V704. The expression is always false on newer compilers. Avoid using 'this == 0' comparison.

08 Сен 2014

Анализатор обнаружил в коде выражение вида 'this == 0'. Данное выражение может оказаться работоспособным в ряде случаев, однако его использование крайне опасно по некоторым соображениям.

Рассмотрим простой пример:

class CWindow {
  HWND handle; 
  public:
  HWND GetSafeHandle() const
  {
    return this == 0 ? 0 : handle;
  }
};

Вообще, вызов метода CWindow::GetSafeHandle() для нулевого указателя 'this' по стандарту С++ ведёт к неопределённому поведению. Однако поскольку во время работы метода не производится доступа к полям этого класса, метод может работать. С другой стороны, существует два возможных неблагоприятных сценария выполнения данного кода. Во-первых, согласно стандарту С++, указатель this никогда не может быть нулевым; следовательно, компилятор может оптимизировать вызов метода, упростив его до:

return handle;

Во-вторых, предположим, что существует следующий код:

class CWindow {
  .... // CWindow из предыдущего примера
};
class MyWindowAdditions {
  unsigned long long x; // 8 bytes
};
class CMyWindow: public MyWindowAdditions, public CWindow {
  ....
};
....
void foo()
{
  CMyWindow * nullWindow = NULL;
  nullWindow->GetSafeHandle();
}

Этот код приведёт к чтению из памяти по адресу 0x00000008. В этом можно убедиться, написав следующую строку:

std::cout << nullWindow->handle << std::endl;

На экран на печать будет выведен адрес 0x00000008, поскольку исходный указатель NULL (0x00000000) был скорректирован таким образом, чтобы указывать на начало подобъекта класса CWindow. Для этого его надо сместить на sizeof(MyWindowAdditions) байт.

Самое интересное, что теперь проверка "this == 0" полностью теряет смысл. Указатель 'this' всегда по меньшей мере равен значению 0x00000008.

С другой стороны, ошибка не проявит себя, если поменять местами базовые классы в объявлении CMyWindow:

class CMyWindow: public CWindow, public MyWindowAdditions{
  ....
};

Всё это может приводить к крайне неочевидным ошибкам.

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

class CWindow {
  HWND handle; 
  public:
  static HWND GetSafeHandle(CWindow * window)
  {
    return window == 0 ? 0 : window->handle;
  }
};

Второй вариант – использование паттерна Null Object, что тоже повлечёт за собой значительный объём работ.

class CWindow {
  HWND handle;
  public:
  HWND GetSafeHandle() const
  {
    return handle;
  }
};
class CNullWindow : public CWindow {
  public:
  HWND GetSafeHandle() const
  {
    return nullptr;
  }
};
....
void foo(void)
{
  CNullWindow nullWindow;
  CWindow * windowPtr = &nullWindow;

  // Выведет 0
  std::cout << windowPtr->GetSafeHandle() << std::endl;
}

Стоит добавить, что данный дефект очень опасен тем, что на его обработку почти никогда нет времени, поскольку "всё и так работает", а затраты на рефакторинг велики. Однако то, что работало годами, может неожиданно дать сбой при малейшем изменении условий: сборка под другую ОС, изменение версии компилятора (в том числе и его обновление) и так далее. Стоит привести следующий пример: компилятор GCC, начиная с версии 4.9.0, научился выбрасывать проверку на неравенство нулю разыменованного выше по коду указателя (см. диагностику V595):

int wtf( int* to, int* from, size_t count ) {
    memmove( to, from, count );
    if( from != 0 )  // <= после оптимизации условие всегда истинно
        return *from;
    return 0;
}

Примеров проблемного кода из реальных приложений, оказавшегося "сломанным" из-за undefined behavior, достаточно много. Стоит привести несколько из них, чтобы подчеркнуть важность проблемы.

Пример N1. Уязвимость в ядре Linux

struct sock *sk = tun->sk;  // initialize sk with tun->sk
....
if (!tun) // <= всегда false
    return POLLERR;  // if tun is NULL return error

Пример N2. Некорректная работа srandomdev():

struct timeval tv;
unsigned long junk; // <= Не инициализирована специально
gettimeofday(&tv, NULL); 
// LLVM: аналог srandom() от неинициализированной переменной,
// т.е. tv.tv_sec, tv.tv_usec и getpid() не учитываются.
srandom((getpid() << 16) ^ tv.tv_sec ^ tv.tv_usec ^ junk);

Пример N3. Синтетический пример, очень наглядно показывающий и возможности компиляторов по агрессивной оптимизации в связи с undefined behavior, и новые возможности "прострелить себе ногу":

#include <stdio.h>
#include <stdlib.h>
 
int main() {
  int *p = (int*)malloc(sizeof(int));
  int *q = (int*)realloc(p, sizeof(int));
  *p = 1;
  *q = 2;
  if (p == q)
    printf("%d %d\n", *p, *q); // <= Clang r160635: Вывод: 1 2
}

Насколько нам известно, на дату выхода этой диагностики вызов проверки this == 0 ещё не проигнорирован ни одним компилятором, однако это – дело времени, поскольку в стандарте С++ явным образом написано (§9.3.1/1): "If a nonstatic member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.". Иными словами, результат вызова любой нестатической функции для класса с this == 0 не определён. Это лишь дело времени, когда компиляторы начнут вместо (this == 0) подставлять false на этапе компиляции.

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

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

close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам