Я использую PVS-Studio для анализа моего тест-кода. Есть часто конструкции вида
const noAnimal* animal = dynamic_cast<noAnimal*>(...);
BOOST_REQUIRE(animal);
BOOST_REQUIRE_EQUAL(animal->GetSpecies(), ...);
Однако я все еще получаю предупреждение V522 There might be dereferencing of a potential null pointer 'animal'
для последней строки.
Я знаю, что можно пометить функции как «не возвращающие NULL», но можно ли также пометить функцию как допустимую проверку NULL или заставить PVS-Studio как-то иначе понять, что animal
не может быть NULL после BOOST_REQUIRE(animal);
?
Это также происходит, если указатель проверяется с помощью любого assert
аромат в первую очередь.
Спасибо за интересный пример. Мы подумаем, что мы можем сделать с BOOST_REQUIRE
макро.
На данный момент я могу посоветовать вам следующее решение:
Где-то после
#include <boost/test/included/unit_test.hpp>
ты можешь написать:
#ifdef PVS_STUDIO
#undef BOOST_REQUIRE
#define BOOST_REQUIRE(expr) do { if (!(expr)) throw "PVS-Studio"; } while (0)
#endif
Таким образом, вы дадите подсказку анализатору, что ложное состояние вызывает прерывание потока управления.
Это не самое красивое решение, но я думаю, о нем стоило рассказать.
Отвечать на большой комментарий — плохая идея, поэтому вот мой подробный ответ на следующую тему:
Хотя это возможно, было бы больно включать это определение в
все тестовые файлы. Кроме того, это не только BOOST_REQUIRE, но
также применяется к assert, SDL_Assert или любому другому пользовательскому макросу пользователя
может использовать.
Следует понимать, что существует три типа тестовых макросов, и каждый должен обсуждаться отдельно.
Макросы первого типа просто предупреждают вас, что в версии Debug что-то пошло не так. Типичный пример assert
макро. Следующий код заставит анализатор PVS-Studio выдавать предупреждение:
T* p = dynamic_cast<T *>(x);
assert(p);
p->foo();
Анализатор укажет на возможную разыменовку нулевого указателя и будет прав. Проверка, которая использует assert
недостаточно, поскольку оно будет удалено из версии выпуска. То есть оказывается, что нет чека. Лучший способ реализовать это — переписать код примерно так:
T* p = dynamic_cast<T *>(x);
if (p == nullptr)
{
assert(false);
throw Error;
}
p->foo();
Этот код не вызовет предупреждение.
Вы можете утверждать, что вы на 100% уверены, что dynamic_cast
никогда не вернется nullptr
, Я не принимаю этот аргумент. Если вы абсолютно уверены, что приведение ВСЕГДА правильно, вы должны использовать более быстрый static_cast
, Если вы не уверены, вы должны проверить указатель перед разыменованием.
Хорошо, хорошо, я понимаю вашу точку зрения. Вы уверены, что с кодом все в порядке, но вам нужна эта проверка с dynamic_cast на всякий случай. ОК, используйте следующий код:
assert(dynamic_cast<T *>(x) != nullptr);
T* p = static_cast<T *>(x);
p->foo();
Мне это не нравится, но, по крайней мере, это быстрее, так как более медленный оператор dynamic_cast будет опущен в версии Release, а анализатор будет хранить молчание.
Переходя к следующему типу макросов.
Макросы второго типа просто предупреждают вас, что что-то пошло не так в версии Debug и используются в тестах. Что отличает их от предыдущего типа, так это то, что они останавливают тестируемый алгоритм, если условие ложно, и генерируют сообщение об ошибке.
Основная проблема с этими макросами заключается в том, что функции не помечены как не возвращаемые. Вот пример.
Предположим, у нас есть функция, которая генерирует сообщение об ошибке, генерируя исключение. Вот как выглядит его объявление:
void Error(const char *message);
И вот как объявляется тестовый макрос:
#define ENSURE(x) do { if (!x) Error("zzzz"); } while (0)
Используя указатель:
T* p = dynamic_cast<T *>(x);
ENSURE(p);
p->foo();
Анализатор выдаст предупреждение о возможной разыменованию нулевого указателя, но код на самом деле безопасен. Если указатель нулевой, то Error
Функция выдаст исключение и, таким образом, предотвратит разыменование указателя.
Нам просто нужно сообщить об этом анализатору, используя одно из средств аннотации функции, например:
[[noreturn]] void Error(const char *message);
или же:
__declspec(noreturn) void Error(const char *message);
Это поможет устранить ложное предупреждение. Итак, как вы можете видеть, в большинстве случаев довольно просто что-то исправить, используя собственные макросы.
Однако может быть сложнее, если вы имеете дело с небрежно реализованными макросами из сторонних библиотек.
Это приводит нас к третьему типу макросов. Вы не можете изменить их, и анализатор не может понять, как именно они работают. Это обычная ситуация, поскольку макросы могут быть реализованы довольно экзотическими способами.
В этом случае вам остается три варианта:
Мы постепенно добавляем поддержку различных сложных макросов из популярных библиотек. Фактически, анализатор уже знаком с большинством конкретных макросов, с которыми вы можете столкнуться, но воображение программистов неисчерпаемо, и мы просто не можем предвидеть каждую возможную реализацию.