2

我正在使用 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如果首先通过任何风味检查指针,也会发生这种情况。

4

2 回答 2

1

用大的评论回复评论是个坏主意,所以这是我对以下主题的详细回复:

虽然这是可能的,但在所有测试用例文件中包含该定义会很痛苦。此外,这不仅限于 BOOST_REQUIRE,还适用于 assert、SDL_Assert 或用户可能使用的任何其他自定义宏。

应该明白,有三种类型的测试宏,每种都应该单独讨论。

第一种类型的宏只是警告您在调试版本中出现了问题。一个典型的例子是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);

这将有助于消除错误警告。因此,如您所见,在大多数情况下使用自己的宏很容易修复问题。

但是,如果您处理来自第三方库的粗心实现的宏,则可能会更棘手。

这将我们引向第三种类型的宏。您无法更改它们,分析器也无法弄清楚它们的工作原理。这是一种常见的情况,因为宏可能以非常奇特的方式实现。

在这种情况下,您可以选择三个选项:

  1. 使用文档中描述的一种误报抑制手段来抑制警告;
  2. 使用我在上一个答案中描述的技术;
  3. 给我们发电子邮件。

我们正在逐渐增加对流行库中各种棘手宏的支持。事实上,分析器已经熟悉了你可能遇到的大多数特定宏,但程序员的想象力是无穷无尽的,我们只是无法预见每一种可能的实现。

于 2017-11-16T08:49:23.117 回答
1

感谢您提供有趣的示例。我们会想,我们可以用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

这样,您将向分析器提示错误条件导致控制流中止。这不是最漂亮的解决方案,但我认为值得告诉你。

于 2017-11-15T13:02:42.390 回答