本主题介绍如何分析流分析报告的 bug

章节目录:

使用流分析

本主题说明如何运行流分析来暴露各种 bug,使用未初始化的内存、空指针解引用、除零、内存和资源泄漏。

运行流分析

流分析是一种新型的静态分析技术,使用包括模拟应用程序执行路径在内的多种分析技术来识别可能导致运行时缺陷的路径。检测到的缺陷包括使用未初始化的内存、空指针解引用、被零除、内存和资源泄漏。

由于这种分析涉及识别和跟踪复杂的路径,因此可以暴露通常能逃过静态代码分析和单元测试的 bug,并且这些 bug 很难通过手动测试或检查发现。流分析在不执行代码的情况下暴露 bug 的能力对于拥有遗留代码库和嵌入式代码的用户而言尤其有效(运行时检测在这种情况下无法检测到此类错误)。

分析头文件

除非头文件包含在被测源文件中,否则 C/C++test 不会直接分析头文件。详细信息请参阅如何分析头文件/分析哪些文件?

分析模板函数

C/C++test 可以对实例化的函数模板和实例化的类模板成员执行静态分析。
详细信息请参阅 C++ 模板函数支持

运行流分析的一般过程是:

  1. 使用您首选的流分析标准分析设置确定或创建测试配置。
  2. 使用首选测试配置启动测试。
  3. 查看并响应结果。
  4. (可选)根据需要调整流分析设置。

以增量模式运行流分析

默认情况下,流分析会对运行的作用域进行完整的分析。针对大型代码库运行时,可能需要相当长的时间。

执行流分析最常见的方法是对每天都有一些变化的单个代码库运行夜间测试。增量分析模式旨在减少在这种典型场景中运行分析所需的时间。在增量分析模式下,流分析会在初始运行期间记住重要的分析数据,然后在后续运行中复用这些数据——仅对已修改或与修改后的代码紧密连接的代码部分重新运行分析。

使用增量分析时,需注意:

  • 首次流分析运行可能比没有增量分析的运行稍慢。这是因为除了对代码库执行完整的分析之外,流分析还会保存数据以便在后续运行中复用。
  • 需要磁盘空间来存储必要的数据。

可以使用测试配置管理器的静态选项卡中的控件启用增量模式。有关可用控件的详细信息,请参阅配置流分析选项

在启用分析数据交换的情况下运行流分析

分析数据交换模式默认启用。在此模式下,分析数据将写入磁盘。分析数据交换使用相同的持久存储,与增量分析的过程类似。如果对大型项目运行分析,那么代表着所分析源代码语义模型的分析数据可能会消耗用于运行流分析的所有内存。若发生这种情况,流分析将从内存中删除部分当前不需要的分析数据,之后再从磁盘中重新读取。通常而言,我们建议在配置了 Xmx JVM 选项的大型 JVM 堆中运行 C++test。这样做是为了最大限度减少交换,从而提高性能。如果内存足够,则可以禁用分析数据交换以加快代码分析。有关如何禁用分析数据交换的信息,请参阅性能选项卡选项主题中的使分析的数据交换到磁盘

介绍内建流分析测试配置

Flow Analysis Fast

该标准流分析测试配置包含所有流分析规则——除了安全规则和需要自定义的规则。该快速配置分析深度“最浅”,比标准和深度配置运行得更快(请参阅下面的描述)。快速配置可以发现一定数量的问题,避免报告大量违规。我们建议使用此配置来对任何新项目实施流分析规则。如果需要更严格的分析,则可以考虑使用 Flow Analysis Standard。

Flow Analysis Standard

“Flow Analysis Standard”测试配置包含所有流分析规则——除了需要自定义的规则。该配置执行更进一步的分析,可发现比“Flow Analysis Fast”更多的 bug。运行 Standard 配置也需要更多的时间。当发现更多 bug 的优先级更高时,我们建议使用此配置。如果您需要重新定义一些分析参数或重新配置任何规则,则可以基于该测试配置进行设置。

Flow Analysis Aggressive

如果您希望 C++test 报告任何可疑代码的违规,那么建议您使用“Flow Analysis Aggressive”测试配置。使用该配置,流分析会在发现任何可疑问题的时候报告违规——即使在某些情况下可能是误报。应用该配置会报告更多的 bug,但也会增加误报的数量。

了解流分析的分析范围

流分析执行可在选择分析的文件范围内跨函数、类、命名空间和编译单元边界的过程间分析。如果有函数调用,并且调用的函数定义在不同文件中,流分析将能够追踪到函数内部的路径——只要包含函数的文件包含在分析范围内。因此,分析的精度高度依赖于分析范围。

以下是定义分析范围的一般准则(选择要在单个会话中分析哪些文件):

  • 不允许将独立的各个项目放在同一个分析范围内。不同的项目对具有相同完全限定名称的类型或具有特定签名的函数可以有不同的定义。如果调用的是一个被多次定义的函数,流分析可能无法追踪到正确的函数调用。
  • 如果想要分析依赖于编译单元 B 的编译单元 A,通常建议将 A 和 B 都包含在分析范围内,以便流分析在分析 A 时可以考虑来自单元 B 的任何必要信息。同样的模式也适用于依赖项目。当然,分析中包含的文件越多,分析耗费的时间就越长。流分析允许只对单个文件进行分析。然而,这种有限的分析可能不太精确。

查看流分析结果

本主题介绍如何分析流分析报告的 bug。

访问结果

在质量任务视图中,流分析任务与静态代码分析违规一起报告。不过流分析的内容和格式与用于静态代码分析违规的内容和格式明显不同。报告的结果将整理到一个按优先级排序的任务列表中,可以按规则分类或严重度查看列表内容。要按严重度查看结果,则打开质量任务下拉菜单,然后选择显示> 详细信息

您可以在质量任务视图的修复静态分析的违规分类中查看通过 GUI 运行分析的结果以及在 IDE 中导入的结果(请参阅将结果导入 UI)。

对于在命令行界面运行的测试,将在静态分析报告部分报告违规信息。

了解有关报告的 bug 类型的更多信息

要查看报告的 bug 类型的详细说明,可在质量任务视图中右键点击 bug 消息,然后从快捷菜单选择查看规则文档。一个黄色的“让行”符号标记了您应该右键点击的节点。

打开引发违规的测试配置

引发违规的测试配置可以从质量任务视图打开:右键点击违规并选择查看测试配置。

从违规中快速访问测试配置对于自定义测试并希望快速禁用不适用规则的团队架构师而言非常有用。从基于服务器的运行导入结果的开发人员可能还需要打开并审查引发违规的测试配置。

了解流路径

在质量任务视图中,每个流分析违规都由一个结构性的流路径表示,精确地描述了导致已发现问题的代码。路径中的每个元素都是在运行时执行的一行代码。如果一个流路径包含对某个函数的调用,则表示该函数调用的元素是一个节点,其子节点表示在被调用函数中的执行流。执行路径中的最后一个元素总是 bug 显现的点。给出完整的路径是为了说明为什么在最后一点会有 bug。

由信息图标标记的流路径元素具有工具提示,可帮助您分析导致相应问题的流程。将鼠标悬停在某个元素上可显示工具提示。

如果要浏览与报告的执行路径相关的代码,可使用质量任务视图工具栏中的下一个任务元素上一个任务元素按钮。

源代码变更如何影响流路径?

如果源代码在流分析运行后发生了变化:

  • 流分析违规将继续使用原流路径的源代码。这确保了显示的是有效的违规路径。
  • 如果流路径中的元素过时(即当前源代码与分析的代码不匹配),将使用特殊的“过时”图标和工具提示进行标记该元素以及违规节点。此外,由于相关源代码不再可用,将禁用双击元素查看相关代码的选项。

当从违规原因到违规点存在多条路径时,路径中可能包含省略号,用于指示路径之间的差异。


简洁与详细路径跟踪视图

默认情况下,使用简洁的路径跟踪视图显示结果,该视图仅显示缺陷路径中的基本可执行语句。该视图提供了报告的各种问题的概述,同时允许深入了解详细信息,以便理解和修复各个问题。此外,还提供了完整的路径。更多详细信息,请参阅简洁与详细结果显示。

识别和理解重要元素

重要的路径元素(相对于发现的违规)用暗色球形图标标记。此外,如果您将光标放在特殊标记的元素上,工具提示将解释为什么认为该元素重要。例如,可能会提示“Null 值来源”、“关键的数据流”或“包含关键的数据流”(函数调用节点包含“重要元素”时的输出)。“重要元素”包括违规原因、违规点和所有元素(这些元素改变了携带导致违规的数据的变量列表)。例如,假设“a”变量在以下代码段中携带一个空指针:

1: b = a;
2: c = k;

如果后来在变量“b”上发现违规,第 1 行则十分关键并被特别标记。

第 2 行与携带不良数据的变量无关,因此不重要,也没有特别标记。

了解和访问违规原因和违规点

违规本身由带有两个标记点的执行路径表示:

  • 违规原因。这是违规的“源头”,通常是导致“不良数据”的原因。例如,在“避免空指针解引用”规则中,违规原因是空指针的源。
  • 违规点。这是使用“不良数据”的点,通常会导致程序中的 bug。对于“避免空指针解引用”规则,该点是持有空指针的变量被解引用的点。

通过右键点击报告的违规(带有黄色警告图标的节点),然后从快捷菜单选择显示违规原因显示违规点

相应的代码将在编辑器中突出显示:

突出显示违规消息引用的源代码

要突出显示流分析违规消息引用的源代码,只需双击该消息即可。将打开一个编辑器,其中与违规相对应的源代码处于选中状态,并以浅蓝色突出显示流路径。任何抛出异常的行都用粉红色标记。所有对违规而言重要的行(违规原因、违规点、关键数据流)都以深色突出显示。如果要浏览与报告的执行路径相关的代码,可使用质量任务视图工具栏中的下一个违规元素上一违规元素按钮。

提示

  • 您可以禁用或抑制不需要检查的规则。详细信息请参阅静态选项卡设置 - 定义静态分析的执行方式
  • 要了解 C++test 包含的流分析规则,可选择 Parasoft> 帮助,打开 C++test 静态分析规则手册,然后浏览可用的流分析分类规则描述文件。

简洁与详细结果显示

简洁视图与详细视图

默认情况下,使用简洁的路径跟踪视图显示结果,该视图仅显示缺陷路径中的基本可执行语句。该视图提供了报告的各种问题的概述,同时允许深入了解详细信息,以便理解和修复各个问题。

此外,还提供了完整的路径。当违规以详细形式显示时,可展开的任务节点包含执行路径上的所有元素,流分析认为检测到的 bug 在该路径上表现出来。换句话说,流分析将显示产生这个 bug 所执行的行的确切顺序。这为您提供了关于发现的问题和流分析的假设的完整信息。有时,这些信息对于理解流分析报告某个问题的原因而言是十分必要的。

而在许多情况下,此类详细信息对于理解报告的问题而言可能不是必需的。此时,简洁形式则更为方便。当违规以简洁形式显示时,可展开的任务节点仅显示完整执行路径上最重要的元素,流分析认为检测到的 bug 在该路径上表现出来。流分析将以下执行路径元素视为重要元素并在简洁视图中显示:

  • 违规原因(通常是不良数据的来源)。
  • 违规点(bug 显现的点)。
  • 抛出异常的元素(因为它们会显著影响控制流程)。
  • 关键数据流程元素(不良数据从一个变量流向另一个变量的点【可能是将不良数据赋值给变量,将不良数据作为参数传递给方法或从方法中返回不良数据】;这些信息是理解为何发生违规的关键)。

下面是详细视图的示例:

以下是同一违规的简洁视图示例:

在编辑器中查看详细路径

即使任务视图只显示最重要的元素,如果选择了违规,并且 IDE 编辑器中显示了相应的代码,那么所有的路径元素都将突出显示。因此,您可以使用简洁视图快速评估问题,然后使用源代码编辑器查看详细的执行路径来了解有关为何发生违规的更多信息。

显示详细视图

显示特定违规详细视图的步骤:

  • 右键点击违规任务节点,然后选择显示完整的违规路径

始终显示详细视图的步骤:

  1. 选择 Parasoft> 首选项
  2. 打开质量任务页面。
  3. 启用显示流分析违规的完整路径选项。

自定义流分析静态分析

本主题说明如何自定义流分析分析——包括检测到的 bug 类型、规则参数化、分析选项以及特定规则检查的资源。

指定要检测的 bug

流分析检测的 bug 类型取决于您在测试配置中启用的规则——与使用编码标准配置静态代码分析一样。基本静态分析设置(每个规则报告的最大任务数等——详细信息请参阅静态选项卡设置 - 定义静态分析的执行方式)适用于静态代码分析和流分析。

例如,如果要禁用或启用流分析规则,可以通过勾选/取消勾选测试配置规则树(静态> 规则树选项卡)中的复选框。

配置规则参数

有些规则是参数化规则,这意味着您可以自定义这些规则的设置,从而使分析过程更加灵活,并根据您的独特项目需求进行定制。因此,流分析甚至可以用于检测与特别的 API 使用相关的违规。

有关如何参数化规则的详细信息,请参阅自定义参数化规则

报告未经验证的违规

报告未经验证的违规是一个常见的规则参数。

要解释这个参数是如何运作的,我们需要先了解发现和报告违规的过程。首先,流分析找到从违规原因(不良或可疑数据来源的点)开始到违规点(不良或可疑数据用于危险或可疑操作的点)结束的路径。当流分析发现这样的路径违规时,通常会通过验证是否可以从违规路径的顶层函数(可调用其他函数的违规路径分层结构中的顶层函数)的开头到达该路径来尝试验证违规。

例如,这里有一种简单的情况,其中违规路径完全位于一个函数内(没有从违规路径调用其他函数):

typedef struct SomeStruct {
    int hashCode; 
} SomeStruct;
extern SomeStruct* getObject();
void check(int initialized)
{
    SomeStruct* obj; 
    if (initialized) {
	obj = getObject();
    } else {
	obj = 0;
    }
    int hashCode = 0;
    // some other actions
    if (initialized) {
	hashCode = obj->hashCode;
    }
    // some other actions
}

当流分析分析这段代码时,首先会发现以下违规:

.C program.c (12): obj = 0; // Null value carrier: obj
.  program.c (14): int hashCode = 0; // Null value carrier: obj
.  program.c (16): if (initialized) { // Null value carrier: obj
.P program.c (17): hashCode = obj->hashCode; // Null value carrier: obj

下一阶段是通过检查是否可以从函数的开头到达违规路径来验证违规。在此阶段,流分析确定只有当“initialized”为 false时,才能为“obj”分配空值。然而,在这种情况下,不可到达发生解引用的行。这个示例说明如何通过从违规的顶层函数开始验证是否可以到达违规路径来进行违规路径验证,从而帮助消除误报。如果示例不同,并且路径从函数开始就可以到达,那么违规将通过验证——在详细路径模式下,流分析将显示违规的完整路径(从顶层函数开始),以解释路径是如何可到达的。

在一些复杂的情况下,受限的分析深度(用于将分析速度保持在合理水平)可能会阻止流分析完成违规验证程序。如果发生这种情况,违规处于未验证状态,流分析不知道是否可以从相应函数开始到达。在这种情况下,报告未经验证的违规参数决定是否报告违规。启用此选项会增加流分析发现的缺陷数量,但也会增加误报数量。

配置流分析选项

此外,通过修改测试配置的静态> 数据流分析高级设定选项卡中的参数,您还可以控制特定的流分析选项,如分析深度、对多线程 API 的支持以及违规报告详细程度。

以下部分介绍了每个子选项卡中的可选项:

性能选项卡选项

  • 增量分析选项控制增量分析功能,在以增量模式运行流分析中有所说明。可选项包括:
    • 启用增量分析:指定是否使用增量分析。
    • 紧凑增量缓存每 [天]:指定运行增量缓存压缩的频率。增量分析针对速度进行了优化;尽管流分析始终保持较小的缓存大小并删除不必要的数据,但源代码更改可能会导致这些缓存包含一些不再使用的数据。按此参数定义定期运行的压缩会删除所有过时的数据。更准确地说,如果自上次压缩以来经过的时间大于此选项指定的天数,则在流分析的增量运行之后立即执行压缩
    • 清除缓存:清除当前工作空间的增量分析缓存。清除缓存后,对工作空间的第一次增量分析需要对代码库进行完整的分析。
  • 分析的深度选项决定了流分析的分析深度。更深入的分析意味着可以发现更多的违规。不过,更深入的分析会影响性能并(少量)增加内存消耗。可选项包括:
    • 最浅的(最快):只发现源代码中最明显的问题。仅限于问题的源头位于发生问题的代码附近的情况。这种类型的分析发现的违规的执行路径通常涉及单个函数中的几行代码,很少会涉及 3 个以上的函数调用。
    • 浅的(快):与“最浅的”分析类型类似,只发现源代码中最明显的问题。然而,该类型产生的任务总数更多,并允许检查更长的执行路径。
    • 标准:发现包含数十个元素的执行路径的复杂问题。标准分析在浅层分析基础上还能查找更复杂的问题,这些问题可能是由单个函数中的不良流程或由被分析项目不同部分中不同函数间的不当交互所导致的。这种类型的分析发现的违规通常可以揭示被分析的源代码中存在的重要错误,并且通常涉及数十行代码。
    • 深(慢):检测与“标准”深度定义的复杂性和性质相同的更多问题。这种类型的分析比标准分析慢。
    • 彻底(最慢):发现更复杂的问题。这种类型的分析将对代码库进行彻底扫描;这需要更多的时间,但可以发现许多非常复杂的问题,这些问题的违规路径可能涉及被扫描应用程序不同部分中的一百多行代码。建议为夜间运行使用此选项。
  • 超时策略选项允许配置超时策略,以确保分析在合理时间内完成。可选项包括:
    • 时间:对给定热点的分析会在话费指定时间后停止。注意:在某些情况下,使用此选项可能会导致报告的违规数量略微不稳定。
    • 指令:在执行指定数量的流分析指令之后,停止对给定热点进行分析。注意:要为您的环境设置合适的指令数量,请查看生成报告的“设置问题”部分中有关超时的信息。
    • 关闭:无超时设置。注意:使用此选项可能需要更多的时间来完成分析。
    默认的超时选项是时间,值设置为 60 秒。要获取有关分析期间发生的流分析超时的信息,可查看分析后生成报告中的设置问题部分。

  • 使分析的数据交换到磁盘:默认启用。流分析将分析所需的数据写入磁盘。交换过程与增量分析类似,并且使用相同的持久存储。如果对不需要大量内存的中小型项目运行流分析分析,或者在内存充足的情况下(例如,对于 64 位系统),您可以禁用此选项来加快分析速度。有关交换功能的描述,请参阅“在启用分析数据交换的情况下运行流分析”部分。

详细度选项卡选项 

在详细度子选项卡中,您可以配置以下选项:

  • 当无法显示原因时,不报告违规:  指定流分析在无法显示原因的情况下是否报告违规。
    一些流分析规则要求流分析检查可到达某一点的所有可能路径,并验证所有这些路径是否都满足某个条件。在这种情况下,一个违规会与多个路径关联(而在简单的情况下,违规仅由一条路径表示)。这类违规中的所有路径都以违规中所有路径共有的违规点结束。不过,不同的路径可能从代码中不同的点开始。每条路径的开始是一个违规原因(代码中的一个点,明确在违规点出现代码中某个条件的违规)。如果多路径违规的不同路径有不同的原因,流分析将只显示违规点(而不是违规原因)。

    与流分析显示的从违规原因开始到违规点的完整路径相比,仅包含违规点的违规信息可能不太容易理解。这就是为什么我们提供了一个选项来隐藏无法显示原因的违规。

    更多详细信息,请参阅下面的示例。

  • 不要报告通过内联汇编代码构成路径的违规:防止报告路径经过内联汇编代码指令的违规。
  • 构建分析数据的报告问题:指定当流分析在构建分析数据时遇到问题时是否报告设置问题。
  • 报告类似违规的程度(即拥有同一个违规点或拥有同一个违规点和违规原因的类似违规):指定流分析是否报告它能够在分析的代码中找到的所有违规,或者是否隐藏其中一些违规。可选项包括:
    • 报告所有类似的违规:报告所有被发现的流分析违规。
    • 具有相同原因和违反点时最多只显示一个违规:防止报告重复的流分析违规。重复违规是指拥有相同违规原因和相同违规点的违规(即使它们的流路径可能不同)。
    • 在每个可疑点最多只显示一个违规:限制针对每个可疑点报告一个违规(针对一个规则)。当详细度设置为这个级别时,流分析性能会快一些。

示例 -“当无法显示原因时,不报告违规”的影响

仅在启用当无法显示原因时,不报告违规的情况下,以下示例代码导致 BD.PB-SWITCH 规则的多原因违规报告:

#include "stdio.h"
enum Figures {
    SPHERE,
    CIRCLE,
    CUBE,
    SQUARE, 
    HIMESPHERE
};
static void guessFigure(int round, int volumetric)
{
    int figure;
    if (round && volumetric) {
	figure = SPHERE; /* CAUSE 1 */  
    } else if (round && !volumetric) {
	figure = CIRCLE; /* CAUSE 2 */
    } else if (!round && volumetric) {
	figure = CUBE; /* CAUSE 3 */
    } else {
	figure = SQUARE; /* CAUSE 4 */
    }
    switch (figure) {
	case SQUARE:
		printf("This is a sphere");
		break;
	case HIMESPHERE:
		printf("This is a hemispere");
		break;
	case CIRCLE:
		printf("This is a circle");
		break;
	case CUBE:
		printf("This is a cube");
		break;
	default:
		printf("This is a square");
		break;
    }
}

终止程序选项卡选项

此选项卡允许定义终止应用程序执行的函数。C/C++ 开发人员有时会使用在发生无法恢复的致命错误时终止应用程序执行的函数。这类函数包含标准库中的 abort() 和 exit() 等。由于流分析分析应用程序的执行流,因此它必须了解通过立即停止应用程序来中断执行流的终止函数。否则,流分析可能会对应用程序流程做出不正确的假设。

流分析了解标准库中定义的终止函数。然而,这通常是不够的,因为非标准库会定义自己的终止函数。如果您的应用程序使用这样的函数,则应该通过在此选项卡中指定自定义终止函数来将该信息传达给流分析。否则,流分析可能会针对经过终止函数的执行路径产生误报。

使用列出了支持的 API 的表格来启用/禁用各种 API 的终止程序,以及自定义包含终止函数的 API。要添加某个库中终止函数的信息,点击添加,键入库的名称,然后按 <Enter> 键。一个新条目将被添加到终止程序 API 表格中。接下来,点击“编辑”按钮,完成打开的表格;该表格包含以下列:

  • 已启用:指定在分析过程中是否应考虑某个内建或自定义终止程序。
  • 完全限定类型名称或命名空间 (通配符):指定特定终止程序的实体。如果此字段留空,则只有在“函数名”列中指定名称的全局函数才会被视为终止程序。
    • 例如:如果终止程序是在 'myNameSpace' 命名空间的 'myClass' 中声明的,则字段值可以是 "myNameSpace::myClass"。或者,如果没有在类型中声明,只属于 'myNameSpace',则可以是 "myNameSpace"。
  • 函数名:指定终止函数的名称。
  • + 在子类中定义:指定子类中的终止函数定义是否也应被视为终止函数。这既适用于实例函数,也适用于非实例函数,并且只有在指定了其完全限定的类型名称时才有意义。

多线程选项卡选项

此选项卡允许定义线程间同步的函数,以及激活/停用已知的多线程函数。此处定义的信息会影响 BD.TRS(多线程和同步性)分类中规则的行为。这些规则将检查在此选项卡上定义和激活的所有函数。

使用列出了支持的 API 的表格来启用/禁用各种 API 的同步函数,以及自定义包含同步函数的 API。要添加某个库中同步函数的信息,点击添加,键入库的名称,然后按 <Enter> 键。一个新条目将被添加到多线程 API 表格中。接下来,点击编辑并完成对话框以定义特定的同步函数。此对话框允许定义以下类型的函数:

  • 用于锁定的函数(例如,获取互斥锁)
  • 用于解锁的函数(例如,释放互斥锁)
  • 睡眠函数
  • 函数等待条件变量

  • 线程创建函数

这些函数的定义与资源非常相似(如指定要检查 BD.RES 规则的资源中所述)。

资源选项卡选项

允许定义 BD.RES 分类(资源)规则应该检查哪些资源。这些规则检查在此选项卡上定义和启用的所有资源是否正确使用。

有关配置这些资源检查设置的详细信息,请参阅以下内容。

指定要进行 BD.RES 规则检查的资源

测试配置管理器的静态> 数据流分析高级选项> 资源选项卡允许定义 BD.RES 分类(资源)规则应该检查哪些资源。这些规则检查在此选项卡上定义和启用的所有资源是否正确使用。

您可以使用该表格来启用/禁用对各种类型资源的检查。添加删除编辑按钮可用于管理自定义资源。

添加某个库中资源信息的步骤:

  1. 点击添加
  2. 键入资源类型的名称。
  3. <Enter> 键。一个新条目将被添加到资源表格中。
  4. 如果合适/需要,禁用不要在应用程序终止时报告违规行为选项。
  5. 点击编辑按钮,完成定义资源分配器、资源关闭器、资源检查器和安全函数的对话框设置。任何一个面板的表格中的任何一行都恰好对应一个函数,并且包含明确标识一个函数(或者——如果使用通配符的话——一系列函数)的足够信息,同时描述函数如何分配/释放资源。下面提供了有关完成此对话框设置的详细信息。

配置资源分配器

资源分配器表格可以用能够产生资源的函数的描述符来设置。该表格包含以下列:

  • 已启用:指定在分析期间是否应考虑分配器。
  • 完全限定类型名称或命名空间(通配符):声明函数的类型或命名空间的完全限定名称。如果要描述在任何类型或命名空间中声明的函数,或者在任何类型之外声明的全局函数,则使用 '*'。
  • 函数名(通配符):分配函数的名称。'*' 可用于表示任意数量的任何符号。
  • 资源参数:指定函数在其一个或多个参数中分配资源。要么指定函数分配的从 1 开始的参数数量,要么使用 '*' 表示分配所有参数。
  • + 在子类中的定义:指示子类中的定义(具有给定名称的函数的定义)是否也应被视为分配器。需注意,该设置适用于实例函数和静态函数。
  • "this" 对象为资源:指示函数在调用函数的对象中分配资源。
  • 返回资源对象:指示函数返回分配的资源。
  • 返回错误的约束值:指定分配失败(如果资源分配器返回一个整数值)情况下的返回值约束。按以下格式输入条件:<比较运算符> <整数值>。例如,如果函数在失败时返回非零值,则在字段中输入“!=0”(不带引号)。如果发生错误时的返回码为 -1,则在此处输入“==-1”。除了“!=”和“==”之外,您还可以使用以下运算符来指定错误条件:“>”、“>=”、“<”和“<=”。

分配函数返回指示分配失败的错误码十分常见。当分配函数返回指向资源的指针时,空指针通常表示分配失败。流分析在寻找资源泄漏时需要了解分配是否成功;这有助于只报告实际发生分配的路径中缺失的释放函数调用。在资源分配器函数返回资源指针的情况下,如果指针不为空指针,则流分析假设资源已成功分配。

配置资源关闭器

资源关闭器表格可以用能够关闭资源的函数的描述符来设置。该表格包含以下列:

  • 已启用:指定在分析期间是否应考虑关闭器。
  • 完全限定类型名称或命名空间(通配符):声明函数的类型或命名空间的完全限定名称。如果要描述在任何类型或命名空间中声明的函数,或者在任何类型之外声明的全局函数,则使用 '*'。
  • 函数名(通配符):关闭函数的名称。'*' 可用于表示任意数量的任何符号。
  • + 在子类中的定义:指示子类中的定义(具有给定名称的函数的定义)是否也应被视为关闭器。需注意,该设置适用于实例函数和静态函数。
  • "this" 对象为资源:指示在调用函数的对象中关闭资源。
  • 资源参数:指定在一个或多个参数中关闭资源。要么指定函数关闭的从 1 开始的参数数量,要么使用 '*' 表示关闭所有参数。

配置资源检查器

资源检查器表格可以用能够检查资源是否打开的函数的描述符来设置。该表格包含以下列:

  • 已启用:指定在分析期间是否应考虑检查器。
  • 完全限定类型名称或命名空间(通配符):声明函数的类型或命名空间的完全限定名称。如果要描述在任何类型或命名空间中声明的函数,或者在任何类型之外声明的全局函数,则使用 '*'。
  • 函数名(通配符):检查函数的名称。'*' 可用于表示任意数量的任何符号。
  • + 在子类中的定义:指示子类中的定义(具有给定名称的函数的定义)是否也应被视为检查器。需注意,该设置适用于实例函数和静态函数。
  • "this" 对象为资源:指示函数在调用函数的对象中检查资源。
  • 资源参数:指定函数在其一个或多个参数中检查资源。要么指定函数检查的从 1 开始的参数数量,要么使用 '*' 表示检查所有参数。
  • 资源打开时的返回值:指定资源打开时检查函数的返回值。可接受值为 truefalse

配置安全函数

安全函数表格可以用函数的描述符来设置,这些函数可以在资源关闭时安全调用,而不会触发 BD-RES-FREE 规则违规。该表格包含以下列:

  • 已启用:指定在分析期间是否应考虑安全函数。
  • 完全限定类型名称或命名空间(通配符):声明函数的类型或命名空间的完全限定名称。如果要描述在任何类型或命名空间中声明的函数,或者在任何类型之外声明的全局函数,则使用 '*'。
  • 函数名(通配符):安全函数的名称。'*' 可用于表示任意数量的任何符号。
  • + 在子类中的定义:指示子类中的定义(具有给定名称的函数的定义)是否也应被视为安全的。需注意,该设置适用于实例函数和静态函数。

包含内建支持的资源

  • 默认情况下,资源表格包含有内建支持的条目。以下是支持资源的列表:目录 (dirent.h)
  • 动态库 (dlfcn.h)
  • 文件 (stdio.h)
  • 'glob' 结果 (glob.h)
    对应于由以下调用分配的 'glob_t' 结构:

    int glob(const char *pattern, int flags,
                    int (*errfunc) (const char *epath, int eerrno),
                    glob_t *pglob);
  • 'hostent' 结构 (hostent.h)
    对应于由以下调用分配的 'glob_t' 结构:

    struct hostent *getipnodebyname(const char *name, int af, int flags, int
    error_num);
    struct hostent *getipnodebyaddr(const void *addr, size_t len, int af, int 
    error_num);
  • 内存 (标准 C)
    由标准 C 分配函数管理的内存。
  • 内存 (标准 C++)
    由 C++ 内存管理运算符 new/delete 管理的内存。
  • 内存 (Windows API)
    由 Windows API 管理的内存。
  • 消息目录 (nl_types.h)
  • 管道 (stdio.h)
  • PThreads (pthread.h)
  • Regexps (regex.h)
  • 套接字 (socket.h、Winsock2.h)
  • 套接字地址结构 (netdb.h)
    由以下调用分配的 'addrinfo' 结构:

    int getaddrinfo(const char *node, const char *service, const struct addrinfo
    hints, struct addrinfo **res);
  • Windows API
    包含项以下资源类型:
    • 内核、用户和 GDI API (Windows.h)
    • 打印后台处理程序 API (Winspool.h)
    • 备份功能 (Sisbkup.h)
    • 等待链遍历 (Wct.h)
    • 网络管理 (Lm.h)

定义自定义资源

有关如何定义自定义资源的示例,参考以下使用非标准资源的纯 C 代码:

/* returns NULL on allocation failure */
void* myAlloc(void);
void myDealloc(void*);
static void createMyResource_Leak()
{
    void* resource = myAlloc();
} /* 'res' is not closed on the path where it is not NULL */
static void createMyResource_NoLeak()
{
    void* resource = myAlloc();
    if (!resource) {
	return; /* no leak here, if res is NULL, it means allocation failed */
    }
    /* use the resource */
    myDealloc(resource);
}

我们想要定义分配/释放资源的方法,以便可以使用流分析来查找这些非标准资源的泄漏情况。为实现这一目的,需要执行以下步骤:

  1. 在资源选项卡中,点击添加并定义新资源的名称。此名称将用于报告与此资源关联的违规。
  2. 禁用不要在应用程序终止时报告违规行为选项。
  3. 点击编辑,指定如何操作此资源。
  4. 按以下方式定义 myAlloc 分配器:
    • 完全限定的类型名称或命名空间:空
    • 方法名称:myAlloc
    • + 在子类中定义:不勾选
    • '"this" 对象为资源:不勾选
    • 返回资源对象:勾选
    • 返回错误的约束值:空
    • 表示资源的参数序号:空
  5. 按以下方式指定释放器:
    • 完全限定的类型名称或命名空间:空
    • 方法名:myDealloc
    • + 在子类中定义:不勾选
    • "this" 对象为资源:不勾选
    • 表示资源的参数序号:1

现在参考一个不同的示例。在该示例中,一个资源打开函数接收一个指向资源句柄的指针作为参数,并使用新分配的资源句柄对其进行初始化。如果分配失败,则返回错误码 -1。

int openMyResource(int* pHandle);
void closeMyResource(int handle);
 
static void openMyResource_Leak()
{
    int handle;
    openMyResource(&handle);
 } // 'res' is not closed
static void openMyResource_NoLeak()
{
    int handle;
    int status = openMyResource(&handle);
    if (status == -1) {
	return; // no leak here, status == -1 indicates allocation failure
    }
    // use the resource
    closeMyResource(handle);
}

现在,我们通过执行以下操作在流分析中添加对此类资源的支持:

  1. 在资源选项卡中,点击添加并定义新资源的名称。此名称将用于报告与此资源关联的违规。
  2. 禁用不要在应用程序终止时报告违规行为选项。
  3. 点击编辑,指定如何操作此资源。
  4. 按以下方式定义 openMyResource 分配器:
    • 完全限定的类型名称或命名空间:空
    • + 在子类中定义:不勾选
    • '"this" 对象为资源:不勾选
    • 返回资源对象:不勾选
    • 返回错误的约束值: ==-1
    • 表示资源的参数序号:1
  5. 按以下方式指定释放器:
    • 完全限定的类型名称或命名空间:空
    • 方法名:closeMyResource
    • + 在子类中定义:不勾选
    • "this" 对象为资源:不勾选
    • 表示资源的参数序号:1

扩展的分析范围选项卡选项 

在执行代码分析时,流分析 会处理在被测源文件和头文件中定义的函数的定义。在测试范围之外的头文件中定义的函数不会被分析,流分析也不知道这些函数的含义。如果流分析需要在测试范围之外的头文件中函数定义的信息,您可以配置以下选项:

要分析的外部文件:指定需要由流分析进行分析的附加头文件的绝对路径。使用通配符指定模式。

要分析的外部函数:指定需要由流分析进行分析的附加函数。在表格中填写以下信息:

  • 已启用:指定在分析期间是否应考虑函数
  • 完全限定类型名称或命名空间(通配符):声明函数的类型或命名空间的完全限定名称。如果要描述在任何类型或命名空间中声明的函数,或者在任何类型之外声明的全局函数,则使用 '*'。
  • 函数名(通配符):函数的名称。'*' 可用于表示任意数量的任何符号。
  • 参数的数量:指定函数的参数数量。'-1' 可用于表示任意数量的参数。
  • + 定义子类:指示子类中的定义(具有给定名称的函数的定义)是否也应包含在内。需注意,该设置适用于实例函数和静态函数。

指定始终分析的函数

总是要分析函数选项允许定义始终进行分析的在执行路径上出现的函数。这有助于确保规则在检查特定路径时,会分析通常不会检查的函数。

选择启用复选框并提供以下信息:

  • 完全限定类型名称或命名空间(通配符):包含函数的类型或命名空间的完全限定名称。
  • 函数名(通配符):函数的名称。
  • + 定义子类:指示是否也应该在子类中考虑该函数。

编译器特定设置选项卡选项

"errno" 值的内部表示:Standarddefineserno 是 int 类型的可修改左值。没有指定 rno 是宏还是用外部链接声明的标识符。实现可以使用全局变量“errno”或“__errno”,或者应用的“(*errno_function())”模式,其中调用的函数名称可能不同。此选项允许您使用正则表达式指定这些变量和函数的名称:

  • 函数名的模式:使用“errno”值时调用的函数的名称。必须使用正则表达式指定名称。
  • 变量名的模式:使用“errno”值时调用的变量的名称。必须使用正则表达式指定名称。

调用来自头文件 <ctype.h> 中函数的内部表示:该标准指定了几个要在头文件 <ctype.h> 中定义的函数。一些实现(例如 C 模式下的 GNU GCC)将这些函数定义为宏,展开后的代码根据某些标志来测试内部数组中的某个元素。可以是全局数组,也可以是函数返回的指针。此选项允许您使用正则表达式指定这些变量和函数的名称:

  • 函数名的模式:内部调用的函数的名称,而不是头文件 <ctype.h> 中函数的名称(用正则表达式定义)。必须使用正则表达式指定名称。
  • 变量名的模式:调用头文件 <ctype.h> 中的某个函数后在内部使用的变量的名称。必须使用正则表达式指定名称。
  • No labels