本主题涵盖如何分析流分析报告的错误。

此章节:

使用流分析

本主题说明如何运行流分析来暴露错误,例如使用未初始化的内存、空指针取消引用、被零除、内存和资源泄漏。

重要

要使用流分析,需要可选的流分析许可证(带有 C++test 自动化版本)。

运行流分析

流分析是一种新型的静态分析技术,它使用多种分析技术(包括对应用程序执行路径的仿真)来识别可能触发运行时缺陷的路径。检测到的缺陷包括未初始化内存的使用、空指针取消引用、零除、内存和资源泄漏。

由于此分析涉及识别和跟踪复杂路径,因此它暴露了通常逃避基于语法的静态代码分析和单元测试的错误,并且很难通过手动测试或检查来发现。流分析在不执行代码的情况下暴露错误的能力对于具有旧代码库和嵌入式代码(在这种情况下无法有效检测或不可能进行此类错误的运行时)的用户特别有价值。

分析标题

除非被测试的源文件包含标头,否则 C/C++test 不会直接分析标头。有关详细信息,请参见 如何分析头文件/分析了哪些文件? 

分析模板函数

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

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

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

使用 cpptestcli 配置批处理模式流分析

定期计划的批处理模式流分析应仅执行内置或自定义的测试配置,即可根据对您的团队重要的流分析规则对项目进行分析。

示例:

  • cpptestcli  -solution “C:\temp\*.sln” –config team://DataFlowAnalysis -publish
参见从命令行界面进行测试 以获取有关配置批处理模式测试的更多详细信息。

增量模式下的运行流分析

默认情况下,流分析对运行的范围进行完整分析。在大型代码库上运行时,这可能会花费大量时间。

执行流分析的最常见方法是在单个代码库上进行夜间测试,该代码每天都会稍有变化。其增量分析模式旨在减少在这种典型情况下运行分析所需的时间。在增量分析模式下,流分析会在初始运行期间存储重要的分析数据,然后在后续运行期间重新使用它们-仅对已修改或紧密连接到已修改代码的部分代码进行运行分析。

使用增量分析时,请记住:

  • 流分析的初始运行可能会比不进行增量分析的运行稍微慢一些。这是因为流分析除了对代码库进行完整的分析之外,流分析还保存了要在后续运行中重用的数据。
  • 需要磁盘空间来存储必要的数据。

可以使用【测试配置】管理器的【静态】标签中的控件启用增量模式。可用的控件在 配置流分析分析选项中进行了详细说明。

启用交换分析数据的运行流分析

默认情况下,交换分析数据模式为启用状态。在这种模式下,分析数据被写入磁盘。交换分析数据使用相同的持久存储,并且以与增量分析类似的过程完成。如果在大型项目上运行分析,则表示所分析源代码的语义模型的分析数据可能会消耗用于运行流分析的所有可用内存。如果发生这种情况,流分析将从内存中删除当前不需要的部分分析数据,之后从磁盘重新读取。


如果有足够的内存可用,则可能会禁止交换分析数据,这可能会加速代码分析。有关如何禁用分析数据交换的信息,请参阅【性能选项卡选项】主题中的 启用将分析数据交换到磁盘项目符号 。

引入内置流分析测试配置

快速流分析

标准流分析测试配置包括所有流分析规则-安全规则和需要自定义的规则除外。快速配置使用 "Shallowest” 的分析深度,并且比标准配置和主动配置运行得更快(请参见下面的描述)快速配置可以发现发现适量的问题,并防止违规数量增加。我们建议使用此配置为任何新项目强制实施流分析规则。如果需要更严格的分析,请考虑切换到【流分析标准】。

流分析标准

【流分析标准】测试配置包括所有流量分析规则-那些需要自定义的规则除外。此配置将执行更深入的分析,并且可能比“快速分析流量”发现更多的错误。标准版也需要更多时间才能运行。当发现更多错误时,建议您优先使用此配置。如果您需要重新定义一些分析参数或重新配置任何规则,则可以将此测试配置用作起点。

流分析侵蚀

如果您希望 C++test 报告任何可疑代码的违规情况,我们建议使用“【流分析】侵蚀”测试配置。此配置允许【流分析】在有可疑问题的任何时间报告违规行为,即使在报告误警率的情况下也是如此。应用此配置将导致报告更多错误,但是也会增加错误警报的数量。

了解【流分析】的分析范围

【流分析】执行过程间分析该过程可以跨函数、类、命名空间和编译单元的边界,这些函数、类、命名空间和编译单元在选择进行分析的文件范围内。如果存在一个函数调用,并且被调用函数在另一个文件中定义,则【流分析】将能够跟踪函数内部的路径-只要包含该函数的文件包含在分析范围中即可。因此,分析的精度在很大程度上取决于范围。

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

  • 切勿将独立项目放在相同的分析范围内。不同的项目可以对具有相同完全限定名称的类型或具有特定签名的函数具有不同的定义。如果调用了多次定义的函数,则【流分析】可能无法将调用追溯到正确的函数中。
  • 如果要分析编译单元 A 并且它依赖于编译单元 B,则通常建议在分析范围中同时包括 A 和 B,以便【流分析】在分析 A 时可以考虑到来自单元 B 的任何必要信息。相同的模式适用于依赖项目。当然,分析中包含的文件越多,分析所需的时间就越长。【流分析】确实仅允许对单个文件进行分析。但是,这种有限的分析可能不太精确。

查看【流分析】结果

本主题介绍如何分析【流分析】报告的错误。

访问结果

将在【质量任务】视图中报告流分析发现以及静态代码分析违规。但是【流分析】的内容和格式与用于违反静态代码分析的内容和格式有很大不同。结果被组织到优先任务列表中,可以按规则类别或严重性查看。要按严重性查看结果,请打开【质量任务】下拉菜单,然后选择 显示> 详情

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

对于从命令行界面运行的测试,在报告的静态分析 部分中报告了违反规则的情况。

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

要查看报告的错误类型的详细说明,请在【质量任务】视图中右键单击该错误消息,然后从快捷菜单中选择查看规则文档。 黄色的 "Yield”符号标记您应右键单击的节点。

打开触发违规的测试配置

可以从【质量任务】视图中打开触发违规的测试配置:右键单击违规,然后选择 查看测试配置

对于正在自定义测试并希望快速禁用不适用的规则的组架构师来说,从违规中快速访问测试配置非常有用。从基于服务器的运行中导入结果的开发人员可能还需要打开并查看触发违规的测试配置。

了解流量路径

在【质量任务】视图中,每个流分析违规都由分层流量路径表示,该流量路径精确描述了导致已确定问题的代码。路径中的每个元素都是在运行时执行的一行代码。如果流量路径调用函数,则表示该函数调用的元素是一个节点,其子节点表示被调用函数内的执行流。执行路径中的最后一个元素总是 bug 出现的地方。提供完整的路径是为了解释为什么在最后一点存在错误。

信息图标标记的流路元素具有工具提示,可帮助您分析导致检测到问题的流。将鼠标悬停在元素上以显示工具提示。

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

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

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

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

该路径可能包含省略号,当从违规原因到违规点的路径不止一条时,该省略号表示路径之间的差异。


【压缩】与【详细路径】跟踪视图

默认情况下,使用打包路径跟踪视图显示结果,该视图仅显示缺陷路径中的基本可执行语句。这提供了所报告的各种问题的快速概述,同时仍然允许您深入了解详细信息以了解和修复每个问题。还提供完整路径演示。更多详细信息请参见 【打包】与【详细】结果

识别和理解重要元素

重要的路径元素(关于发现的违规)用黑色的球图标标记。此外,如果将光标放在这些特殊标记的元素之一上,则工具提示将说明为什么将该元素视为重要元素。例如,它可能显示“空值源”、“关键数据流”或“包含关键数据流”(如果函数调用节点包含“重要元素”,则为输出)。“重要元素”包括违规原因,违规点以及所有更改携带导致违规的数据的变量列表的元素。例如,假设以下摘录中的变量 "a” 带有空指针:

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

第 1 行很重要,以防以后在变量 "b” 上发现违规情况,特意被标记。

第 2 行与携带错误数据的变量无关,因此并不重要,没有特意标记。

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

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

  • 违规原因。违规“来源”。通常,这是“不良数据”的原因。例如,在“避免空指针取消引用”规则中,违规原因是空指针的来源。
  • 违规点这就是“不良数据”用法的关键点,通常会导致程序中的错误。对于“避免空指针解引用”规则,这是持有空指针的变量被解引用的点。

右键单击报告的违规(带有黄色警告图标的节点),然后从快捷菜单选择显示违规原因显示违规点。可以轻松访问违规原因和违规点。

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

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

Visual Studio 2005 和更高版本中提供了此功能。

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

提示

  • 您可以禁用或抑制不想检查的规则。有关详细信息,请参见。 静态选项卡设置 - 定义如何执行静态分析.
  • 要了解 C++test 附带的流分析规则,请选择Parasoft> 帮助, 打开C++test 静态分析规则 文档然后浏览可用的流分析类别规则描述文件。

打包与详细结果显示

【打包】与【详情】视图

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

还提供完整路径演示。当以详细形式显示违规时,可扩展任务节点将包含执行路径的所有元素,流分析会在该元素上认为检测到的错误已显现出来。换句话说,流分析将显示执行该错误的准确行顺序。这为您提供了关于发现的问题以及流分析假设的完整信息。有时,此信息对理解流分析为什么报告可能的问题很重要。

在许多情况下,此类详细信息对于理解所报告的问题可能不是必需的。在这些情况下,打包的形式很方便。当以打包形式显示违规时,可扩展任务节点仅显示完整执行路径中最重要的元素,流分析会在该路径上认为检测到的错误已显现出来。流分析将以下执行路径元素视为重要元素,并在【打包】视图中显示:

  • 违规原因(通常是产生不良数据的原因)。
  • 违规点(漏洞出现的点)。
  • 元素抛出异常(因为它们会明显影响控制流)。
  • 关键数据流元素(不良数据从一个变量流到另一个变量的点[可能是将不良数据分配给变量,将不良数据作为参数传递给方法,或者从方法中返回了不良数据];此信息对于理解违规行为如何发生至关重要 )。

这是详情视图的示例:

这是相同违规的打包视图的示例:

在编辑器中查看详细路径

即使任务视图仅显示最重要的元素,如果选择了违规并在 IDE 编辑器中显示了相应的代码,则所有路径元素也会突出显示。因此,您可以使用打包视图快速评估问题,然后如果需要更多违规发生方式的信息,请使用源代码编辑器查看详细的执行路径。

显示详情视图

要显示特定违规的详情视图:

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

要始终显示详情视图:

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

自定义流分析静态分析

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

指定要检测的错误

流分析检测到的错误的类型取决于您在【测试配置】中启用的规则,这与使用编码标准配置静态代码分析的方式几乎相同。基本的静态分析设置(每个规则等报告的最大任务数-有关详细信息,请参阅 静态选项卡设置 - 定义如何执行静态分析 ) 适用于静态代码分析和【流分析】分析。

例如,如果要禁用或启用流分析规则, 可以通过选中/清除【测试配置】规则树 (在 静态> 规则树 选项卡中的框来启用或禁用它。

配置规则参数

一些规则已参数化,这意味着您可以自定义它们的设置,以使分析过程更加灵活并针对您的独特项目需求进行定制。结果,流分析甚至可以用于检测与使用特定的 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
}

当 Flow Analysis 分析此代码时,首先会发现以下违规:

.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

下一步是通过检查从函数开始是否可以到达违规的路径来验证违规。在此阶段,Flow Analysis 确定仅当 "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。要从某个库添加有关同步函数的信息,请单击 Add,输入库的名称,然后按 <Enter>键。新条目将添加到多线程 API 表中。接下来,单击 Edit 并完成对话框以定义特定的同步函数。该对话框允许您定义以下类型的函数:

  • 锁定函数(例如获取互斥锁)
  • 解锁函数(例如释放互斥锁)
  • Sleep 函数

这些函数的定义与资源(在 指定资源以检查 BD.RES 规则中所述)大致相同。

【资源】选项卡选项

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

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

指定用于检查 BD.RES 规则的资源

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

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

要添加有关来自特定库的资源的信息:

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

配置资源分配器

资源分配器 表可以使用可以产生资源的函数的描述符来完成。该表包括以下列:

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

分配函数返回错误代码以指示分配失败是很常见的。当分配函数返回指向资源的指针时,NULL 指针通常表示分配失败。当流分析寻找资源泄漏时,它需要了解分配是成功还是失败。 这有助于它仅报告在实际发生分配的路径上对重新分配函数的丢失调用。在资源分配器函数返回指向资源的指针的情况下,流分析假定如果指针不为 NULL,则资源已成功分配。

配置资源关闭器

资源关闭器 表可以使用可以闭资源的函数的描述符来完成。该表包括以下列:

  • 已启用: 指定在分析过程中是否应考虑关闭器。
  • 完全限定的类型名称或命名空间(通配符): 声明函数的类型或命名空间的标准名称。如果要描述以任何类型或名称空间声明的函数,或以任何类型外部声明的全局函数,请使用 '*'。
  • 函数名称(通配符): 关闭函数的名称。'*’可用于表示任意数量的任何符号。
  • + 子类中的定义:一个指示是否也应考虑子类中(具有给定名称的函数的)定义的复选框。请注意这适用于实例和静态函数。
  • "this"对象是资源: 一个指示该函数在调用该函数的对象中分配了资源 close 的复选框,
  • 资源参数: 指定关闭一个或多个参数中的资源。在这种情况下,或者指定该函数关闭的基于 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++ 内存管理操作符新建/删除管理的内存。
  • 内存(Windows API)
    由 Windows API 管理的内存。
  • 消息目录 (nl_types.h)
  • Pipes (stdio.h)
  • PThreads (pthread.h)
  • Regexps (regex.h)
  • Sockets (socket.h, Winsock2.h)
  • 套接字地址结构 (netdb.h)
    对应于通过调用分配给以下地址的 'addrinfo' 结构:

    int getaddrinfo(const char *node, const char *service, const struct addrinfo
    hints, struct addrinfo **res);
  • Windows API
    包括以下资源类型:
    • Kernel、 User 和 GDI API (Windows.h)
    • 后台打印程序 (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 作为分配器:
    • 完全限定的类型名称或命名空间:empty
    • 方法名称: myAlloc
    • + 子类中的定义:unchecked
    • '"this" 对象是资源: unchecked
    • 返回资源对象: checked
    • 错误的返回值约束: empty
    • 代表资源的参数编号: empty
  5. 指定如下所示的解除分配器:
    • 完全限定的类型名称或命名空间:empty
    • 方法名称: myDealloc
    • + 子类中的定义:unchecked
    • "this" 对象是资源: unchecked
    • 代表资源的参数编号: 1

现在让我们考虑一个不同的例子。Resource-opening 函数将指向 resource handle 的指针作为参数接收,并使用新分配的 resource handle 对其进行初始化。如果分配失败,则返回错误代码 -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 作为分配器:
    • 完全限定的类型名称或命名空间:empty
    • + 子类中的定义:unchecked
    • '"this" 对象是资源: unchecked
    • 返回资源对象:unchecked
    • 错误的返回值约束: ==-1
    • 代表资源的参数编号:1
  5. 指定如下所示的解除分配器:
    • 完全限定的类型名称或命名空间:empty
    • 方法名称: closeMyResource
    • + 子类中的定义:unchecked
    • "this" 对象是资源: unchecked
    • 代表资源的参数编号: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