Clang静态分析器跳过了一些检查。

5
我正在使用Clang静态分析器4.0.0。 对于以下示例:
int fun(){

    int aa = 1,bb = 0;
    int cc = aa/bb; // 1) devide by zero. // Reported by clang

    int *pt = nullptr;
    int a = *pt;    // 2) null pointer dereference. // NOT Reported by clang

    int b;
    int c = a + b;  // 3) Unused initialization. // Reported by clang

    return cc;
}

Clang静态分析器仅报告了问题1和3,并跳过了问题2。

但如果我改变问题的顺序,就像这样

int fun(){

    int *pt = nullptr;
    int a = *pt;    // 1) null pointer dereference. // Reported by clang

    int aa = 1,bb = 0;
    int cc = aa/bb; // 2) devide by zero. // NOT Reported by clang

    int b;
    int c = a + b;  // 3) Unused initialization. // Reported by clang

    return cc;
}

然后,clang静态分析器报告1和3并跳过2。
我正在使用以下命令运行clang静态分析器:
clang-check.exe -analyze D:\testsrc\anothercpp.cpp 这是非常不一致的行为。不管问题的顺序如何,其中一个问题会被跳过。 此外,我只使用clang 5.0.1检查了这种情况得到了相同的结果。
有人知道为什么静态分析器会出现这种情况吗?
提前谢谢。
-Hemant

使用最新的5.0.1版本进行检查,结果相同。 - Hemant
1
我想知道这是否根本不是随机的,而可能与第一个问题有关,已经潜在地引起了各种混乱(您的任何一个第一个问题都是未定义行为,并且在大多数系统上会陷阱)。这可能会影响后续分析。我还可以想象,在该路径上关闭了进一步的流敏感检查(跟踪变量的值),因为它们可能会普遍报告虚假错误。错误3可以可靠地检测,而无需执行该类型的分析,因此始终会报告。但那只是猜测。 - PaulR
看起来分析器在某些检查的第一次出现后停止处理。不确定具体是如何完成的,但如果他们进行两次扫描——其中一个是“空指针解引用”和“除以零”,则在第一个错误后就会中断。您甚至可以尝试连续两次出现相同的问题——例如,在两个“除以零”之间停止。 - Mihayl
如果我在第一个空指针解引用之后添加第二个空指针解引用,那么第二个不会被报告为空指针解引用,而是未使用的变量。 - Hemant
如果您想深入了解,请访问https://github.com/llvm-mirror/clang/tree/master/lib/StaticAnalyzer - Mihayl
2个回答

4
看了一下代码,您观察到的行为似乎是按设计来的。当DereferenceChecker发现空指针解引用并报告错误时,它会创建一个“错误节点”,停止进一步的路径敏感分析。
void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
                                   CheckerContext &C) const {
  // Generate an error node.
ExplodedNode *N = C.generateErrorNode(State);

CheckerContext::generateErrorNode被文档化为停止程序中给定路径的探索。
  /// \brief Generate a transition to a node that will be used to report
  /// an error. This node will be a sink. That is, it will stop exploration of
  /// the given path.
  ///
  /// @param State The state of the generated node.
  /// @param Tag The tag to uniquely identify the creation site. If null,
  ///        the default tag for the checker will be used.
  ExplodedNode *generateErrorNode(ProgramStateRef State = nullptr,
                                  const ProgramPointTag *Tag = nullptr) {
    return generateSink(State, Pred,
                       (Tag ? Tag : Location.getTag()));
}

这是有道理的,因为像空指针解引用这样的严重错误发生后,关于程序的很少有意义的预测可以被做出。由于在C++中空指针解引用是未定义行为,所以标准允许发生任何事情。只有通过查看程序和运行环境的细节,才能做出更多的预测。然而,这些预测可能超出了静态分析器的范围。

在实践中,您只能一次修复一个错误,并且可能会继续修复错误,直到静态分析器停止发出有效的投诉。

检测未使用的变量不需要路径敏感分析。因此,这种类型的检查器仍将起作用。


1
这一切表明,你不能仅仅为静态分析器编写几个原始的人工测试。我在文章“为什么我不喜欢合成测试”中详细讨论了这一点。很可能由于某种内部原因(但肯定不是因为错误),Clang分析器跳过了空解引用操作后面的代码片段,这显然会导致未定义行为。或者它可能会跳过除零操作。这并没有错。实际上,空解引用或除零后面写什么都无所谓:随后的代码根本不会执行。因此,这个问题与编译器/分析器的故障无关,而是由于像这样粗心地编写测试所导致的结果。编写一个良好的诊断测试是一项困难的工作,需要你小心谨慎,并注意许多微妙的细节。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接