努力获得更好的 C++ 代码,第一部分:数据流分析基础知识
CLion 提供了一个内置数据流分析器,它会在您编写代码时持续运行并帮助提高代码质量。 它可以显示各种代码问题,这些问题随后可能导致运行时问题、安全漏洞和其他漏洞。 举例来说,这些有用的检查包括常量条件、死代码、null 指针取消引用、内存泄漏和数组索引问题。 我们将发布一系列博文来说明 CLion 中某些检查的运作方式。
今天,我们将介绍数据流分析的基础知识,包括它的一般运作方式,同时介绍几个真实的例子,以帮助您编写出更好的代码。
控制流图
所有数据流检查都依赖于控制流图。 它是一个图表,其中顶点是程序中的语句,边是这些语句之间的控制流跳转(直接代码执行、条件跳转、循环、中断、转到等)。
例如,右侧的控制流图表示左侧的 foo
函数:
CLion 会为每个函数构建相应的图。 每个图都有一个开始节点和一个退出节点,它们对应于函数的入口和出口。 通过访问从开始节点到退出节点的图节点,CLion 可以收集一些有价值的信息。
例如,CLion 会记住每条语句的每个变量中可以存储哪些值。 在上述示例中,CLion 知道在节点 0 和 1 处,形参 x
始终等于 1。 这是因为函数 foo
只有一个调用点,它会传递实参中的值 1。 因此,CLion 得出结论,节点 1 处的条件 x == 1
将始终为 true
,因此控制流永远不会到达节点 3。 在节点 4 处,变量 y 只能保存值 2,因为控制流只能来自节点 2,永远不会来自节点 3。 因此,CLion 得出结论:
- 函数
foo
始终返回值 2 - 条件
x == 1
始终为true
- 语句
y = 3
永远无法访问
现在,我们来看一个更复杂的示例:
这里,我们有两个 if
块,第一个块的执行方式会影响第二个块的功能。 为了支持这种评估,CLion 将 if
语句的 exit 语句拆分为两个不同的上下文:
控制流图的后续节点重复。 它们出现两次,第一次用于 if
语句的 Then
分支,第二次用于 Else
分支。 在第一个“克隆”变量中,x
保存值 1(因为它对应于 if
语句的正分支),y
保存值 2(它存储在节点 2 中)。 在第二个“克隆”变量中,x ! = 1
,y
为 3。
第二个条件 x == 1
,对应于两个克隆的节点 4 和 5。 在节点 4 处,条件始终保存 true,因为 x == 1
。 同时,在节点 5 处,它始终为 false。 因此,节点 8 和 10 永远无法访问,条件 y == 2
只有一个可访问的克隆节点,即节点 9。 在此节点处,y ! = 2
,因此,此条件始终为 false。
数据流分析的实际运作情况
我们来看看这些技术如何帮助 CLion 在 C++ 程序中发现细微的 bug! 我们决定分析 Z3 定理证明器,以下是我们在 CLion 中的数据流分析结果。
这里,变量 u
被初始化为 null_lpvar
,然后可能被重新指定至相同的值(因为 if
条件中的 j == null_lpvar
)。 因此,条件 u == null_lpvar
始终为 true
。 由于这条 if
子句的 true
分支中有返回,所有后续代码都被标记为无法访问(报告为 #6951):
下面是另一个示例。 这里,无符号变量 i
始终等于或大于零,并且在 else
分支中,它不为零。 因此,i > 0
条件始终为 true(报告为 #6952):
在这篇博文中,我们介绍了我们的一项数据流检查 – 常量条件。 CLion 还提供了许多其他数据流检查,我们将在接下来的博文中介绍其中一些。
您认为代码分析在哪些情况下有用? 请在下面的评论中与我们分享您的例子!
本博文英文原作者: