12

是否有好的工具可以自动检查 C++ 项目的编码约定,例如:

所有抛出的对象必须是从 std::exception 派生的类(即throw 42;throw "runtime error";将被标记为错误,就像throw std::string("another runtime error");或抛出任何其他非从 std::exception 派生的类型一样)

最后,我正在寻找类似Cppcheck的东西,但添加新检查的方法比破解检查工具的源代码更简单......甚至可能是带有漂亮的小 GUI 的东西,它允许您设置规则,将它们写入磁盘并使用 Eclipse 等 IDE 或Jenkins等持续集成服务器中的规则集。

4

3 回答 3

10

我在当前项目中运行了许多静态分析工具,以下是一些关键要点:

  • 我使用Visual Lint作为运行所有这些工具的单一入口点。VL 是 VS 运行第三方静态分析工具的插件,允许从报表到源代码的单击路由。除了支持在报告的不同级别错误之间进行选择的 GUI 之外,它还提供自动背景分析(告诉您在进行过程中已修复了多少错误)、单个文件的手动分析、彩色编码错误显示和图表工具。当您尝试添加新的静态分析工具时,VL 安装程序非常漂亮并且非常有用(如果您想使用 Google cpplint 并且没有预安装 Python,它甚至可以帮助您从 ActiveState 下载 Python!)。您可以在此处了解有关 VL 的更多信息:http://www.riverblade.co.uk/products/visual_lint/features.html

  • 在可以使用 VL 运行的众多工具中,我选择了三个使用原生 C++ 代码的工具:cppcheck、Google cpplint 和 Inspirel Vera++。这些工具具有不同的功能。

  • Cppcheck:这可能是最常见的,我们都用过。所以,我会掩盖细节。可以说它捕获了错误,例如对非原始类型使用后缀增量,在应该使用 empty() 时使用 size() 发出警告,变量范围缩小,类定义中成员的名称限定不正确,初始化顺序不正确类成员、缺少初始化、未使用的变量等。对于我们的代码库,cppcheck 报告了大约 6K 错误。有一些误报(例如未使用的功能),但这些都被抑制了。您可以在此处了解有关 cppcheck 的更多信息:http: //cppcheck.sourceforge.net/manual.pdf

  • Google cpplint:这是一个基于 python 的工具,用于检查您的源代码是否存在样式违规。可以在此处找到完成此验证的样式指南:http: //google-styleguide.googlecode.com/svn/trunk/cppguide.xml(基本上是 Google 的 C++ 样式指南)。Cpplint 在我们的代码库中产生了大约 104K 的错误,其中大多数错误与空格(缺失或额外)、制表符、大括号位置等有关。一些可能值得修复的是:C 样式转换、缺少标题。

  • Inspirel Vera++:这是一个用于验证、分析和转换 C++ 源代码的可编程工具。这在功能上类似于 cpplint。可以在此处找到可用规则的​​列表:http: //www.inspirel.com/vera/ce/doc/rules/index.html,可以在此处找到类似的可用转换列表:http://www。启发.com/vera/ce/doc/transformations/index.html。可以在此处找到有关如何添加自己的规则的详细信息:http: //www.inspirel.com/vera/ce/doc/tclapi.html。对于我们的项目,Vera++ 发现了大约 90K 个问题(针对 20 条奇怪的规则)。

于 2012-05-03T10:46:59.480 回答
6

即将到来的状态:来自 Google 的 Manuel Klimek 正在 Clang 主线中集成 Google 开发的用于查询和转换 C++ 代码的工具。

  • 工具基础设施已经布置好,它可能会填满,但它已经可以正常工作了。主要思想是它允许您定义操作并在所选文件上运行这些操作。

  • Google 创建了一组简单的 C++ 类和方法,以允许以友好的方式查询 AST:AST Matcher 框架,它正在开发中,最终将允许非常精确的匹配。

它目前需要创建一个可执行文件,但代码是作为库提供的,因此无需对其进行编辑,并且一次性转换工具可以在单个源文件中处理。


Matcher 的示例(在此线程std::string中找到):目标是从(使用默认分配器)的结果中找到对 forms 的构造函数重载的调用std::string::c_str(),因为它可以用简单的副本代替。

ConstructorCall(
    HasDeclaration(Method(HasName(StringConstructor))),
    ArgumentCountIs(2),
    // The first argument must have the form x.c_str() or p->c_str()
    // where the method is string::c_str(). We can use the copy
    // constructor of string instead (or the compiler might share
    // the string object).
    HasArgument(
        0,
        Id("call", Call(
            Callee(Id("member", MemberExpression())),
            Callee(Method(HasName(StringCStrMethod))),
            On(Id("arg", Expression()))
        ))
    ),
    // The second argument is the alloc object which must not be
    // present explicitly.
    HasArgument(1, DefaultArgument())
)

与 ad-hoc 工具相比,它非常有前途,因为它使用 Clang 编译器 AST 库,因此不仅可以保证无论使用的宏和模板内容多么复杂,只要您的代码编译它就可以分析;但这也意味着可以表达依赖于重载解析结果的复杂查询。

此代码从 Clang 库中返回实际的 AST 节点,因此程序员可以在源文件中精确定位位和尼特,并根据需要进行编辑以调整它。

已经讨论过使用文本匹配规范,但是认为从 C++ API 开始会更好,因为它会增加很多复杂性(和自行车脱落)。我希望 Python API 会出现。

于 2012-05-03T11:43:08.590 回答
3

“风格检查器”的关键问题是风格就像艺术:每个人对什么是好的风格和什么不是好的风格都有不同的看法。这意味着风格检查器将始终需要根据当地的艺术品味进行定制。

要做到这一点,需要一个完整的 C++ 解析器,它可以访问符号定义、范围规则和理想的各种流分析。AFAIK,CppCheck 不提供准确的解析或符号表定义,因此它的错误检查不能既深入又正确。我认为 Coverity 和 Fortify 使用 EDG 前端提供了类似的东西。我不知道他们的工具是否提供对符号表或数据流分析的访问。铿锵而来。

您还需要一种编写样式检查的方法。我认为所有工具都提供对 AST 和符号表的访问,并且您可以自己编写检查代码,但要以熟悉 AST 为代价,这对于像 C++ 这样的大型语言来说很难。我认为 Coverity 和 Fortify 有一些类似 DSL 的方案来指定一些检查。

如果要修复样式不正确的代码,则需要可以修改代码表示的东西。Coverity 和 Fortify 不提供此 AFAIK。我相信 Clang 确实提供了修改 AST 和重新生成代码的能力。您仍然必须对 AST 结构有相当深入的了解才能编写树黑客逻辑并使其正确。

我们的DMS 软件再造工具包及其C++ 前端提供了这些功能中的大部分。使用其 C++ 前端,DMS 可以解析 ANSI C++11、GCC4(带有 C++11 扩展)和 MSVS 2010(带有 C++11 扩展)[2021 年 5 月更新:现在是完整的 C++17 和大部分C++20] 构建具有完整类型信息的 AST 和符号表。也可以询问任意表达式 AST 节点的类型。目前,DMS 计算 C++ 的控制流而不是数据流。

一个 AST API 允许您在程序上编写任意检查;或更改 AST 以解决问题,然后 DMS 的漂亮打印机可以重新生成完整的、可编译的源文本,其中包含注释和保留的文字格式信息(例如,数字的基数等)。你必须知道 AST 结构才能做到这一点,就像其他工具一样,但它更容易知道,因为它与 DMS C++ 语法规则同构。C++ 前端带有我们的 C++ 语法。[DMS 使用 GLR 解析器使这成为可能]。

此外,可以使用 DMS 的规则规范语言,使用 C++ 本身的表面语法来编写模式和转换。可以将 OP 的“不抛出非 STL 异常”编码为

 pattern nonSTLexception(i: IDENTIFIER):statement
   = " throw \i; " if ~derived_from_STD_exception(i);

(元)引号内的内容是带有一些模式匹配转义的 C++ 源代码,例如,“\i”指的是占位符变量“i”,根据规则它必须是 C++ IDENTIFIER;整个“扔 \i;” 子句必须是 C++“语句”(C++ 语法中的非终结符)。规则本身主要表达要匹配的语法,但可以调用应用于匹配子树的语义检查(例如“~is_derived_from_STD_exception”)(在这种情况下,无论“\i”匹配什么)。

在编写这样的模式时,您不必知道 AST 的形状;模式知道它,它会自动匹配。如果您曾经编写过 AST walkers,您将体会到这是多么方便。

匹配知道 AST 节点,因此知道精确位置(文件/行/列),这使得生成具有精确位置信息的报告变得容易。

您需要向 DMS 添加一个自定义例程“inherits_from_STD_exception”,以验证传递给该例程的标识符树节点是(根据 OP 需要)从 std::exception 派生的类。这需要在符号表中找到“std::exception”,并验证标识符树节点的符号表条目是一个类声明,并从其他类声明传递继承(通过以下符号表链接),直到出现 std::exception找到符号表条目。

DMS 转换规则本质上是一对模式,“如果你看到这个,就用那个替换它”。

我们已经为 COBOL 和 C++ 构建了几个带有 DMS 的自定义样式检查器。它仍然是相当多的工作,主要是因为 C++ 是一种非常复杂的语言,您必须仔细考虑检查的确切含义。

更棘手的检查和那些开始陷入深度静态分析的测试需要访问控制和数据流信息。DMS 现在为 C++ 计算控制流,我们正在研究数据流分析(我们已经为 Java、IBM Enterprise COBOL 和各种 C 方言完成了这项工作)。分析结果被绑定回 AST 节点,这样人们就可以使用模式来查找样式检查的元素,然后在需要时按照数据流将元素绑定在一起。

当使用 DMS (或者实际上使用任何其他以任何半准确方式处理 C++ 的工具)说完所有的事情时,编码额外或复杂的样式检查不太可能是“方便的”。您应该希望“可能具有良好的技术背景”。

于 2012-05-04T04:14:14.323 回答