g_d*_*iel 11 c++ static-analysis
有没有很好的工具来自动检查C++项目的编码约定,例如:
所有抛出的对象都必须是从std :: exception派生的类(即throw 42;或者throw "runtime error";将被标记为错误,就像throw std::string("another runtime error");或者抛出任何其他不是从std :: exception派生的类型)
最后,我正在寻找像Cppcheck这样的东西,但是添加新检查的方法比攻击检查工具的源代码更简单...甚至可以使用一个很好的小GUI来设置规则,将它们写入磁盘并使用Eclipse中的规则集或像Jenkins这样的持续集成服务器.
我在当前的项目中运行了许多静态分析工具,这里有一些关键的要点:
我使用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. inspirel.com/vera/ce/doc/transformations/index.html.有关如何添加自己的规则的详细信息,请访问:http://www.inspirel.com/vera/ce/doc/tclapi.html.对于我们的项目,Vera ++发现了大约90K的问题(针对20多条规则).
在即将到来的状态中:来自谷歌的Manuel Klimek正在整合Clang主线,这是一种在谷歌开发的用于查询和转换C++代码的工具.
工具基础设施已经布局,它可能已经填满,但它已经正常运行.主要思想是它允许您定义操作并在所选文件上运行这些操作.
Google创建了一组简单的C++类和方法,允许以友好的方式查询AST:AST Matcher框架,它正在开发中,最终将允许非常精确的匹配.
它需要在此刻创建可执行文件,但代码以库的形式提供,因此无需编辑它,并且可以在单个源文件中处理一次性转换工具.
匹配器的示例(在此线程中找到):目标是查找std::string对结果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())
)
Run Code Online (Sandbox Code Playgroud)
与ad-hoc工具相比,它非常有前景,因为它使用了Clang编译器AST库,因此不管它使用的宏和模板内容有多复杂,只要你的代码编译它就可以被分析; 但它也意味着可以表达依赖于重载决策结果的错综复杂的查询.
此代码从Clang库中返回实际的AST节点,因此程序员可以在源文件中精确定位位和nits,并根据需要进行编辑以进行调整.
已经讨论过使用文本匹配规范,但是从C++ API开始被认为是更好的,因为它会增加很多复杂性(和自行车脱落).我希望Python API能够出现.
“风格检查器”的关键问题是风格就像艺术:每个人对于什么是好风格、什么不好风格都有不同的看法。这意味着风格检查器总是需要根据当地的艺术品味进行定制。
为了正确地做到这一点,需要一个完整的 C++ 解析器,可以访问符号定义、范围规则以及理想情况下的各种流分析。AFAIK,CppCheck 不提供准确的解析或符号表定义,因此它的错误检查不能既深入又正确。我认为 Coverity 和 Fortify 使用 EDG 前端提供了类似的东西;我不知道他们的工具是否提供对符号表或数据流分析的访问。叮当也跟着来了。
您还需要一种编写样式检查的方法。我认为所有工具都提供对 AST 和符号表的访问,并且您可以手动编写自己的检查代码,但代价是深入了解 AST,这对于像 C++ 这样的大语言来说很难。我认为 Coverity 和 Fortify 有一些类似 DSL 的方案来指定一些检查。
如果您想修复样式不正确的代码,您需要可以修改代码表示的东西。Coverity 和 Fortify 不提供此功能。我相信 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 的 beautifulprinter 可以重新生成完整的、可编译的源文本,其中包含注释和保留的文字格式信息(例如数字的基数等)。你必须了解 AST 结构才能做到这一点,就像其他工具一样,但它更容易了解,因为它与 DMS C++ 语法规则同构。C++ 前端带有我们的 C++ 语法。[DMS 使用 GLR 解析器使这成为可能]。
此外,还可以使用 DMS 的规则规范语言以及 C++ 本身的表面语法来编写模式和转换。人们可能会将 OP“不要抛出非 STL 异常”编码为
pattern nonSTLexception(i: IDENTIFIER):statement
= " throw \i; " if ~derived_from_STD_exception(i);
Run Code Online (Sandbox Code Playgroud)
(元)引号内的内容是带有一些模式匹配转义的 C++ 源代码,例如,“\i”指的是占位符变量“i”,根据规则它必须是 C++ 标识符;整个“扔 \i;” 子句必须是 C++“语句”(C++ 语法中的非终结符)。规则本身主要表达要匹配的语法,但可以调用应用于匹配子树(在本例中,无论“\i”匹配什么)的语义检查(例如“~is_driven_from_STD_exception”)。
在编写此类模式时,您不必知道 AST 的形状;只需知道 AST 的形状即可。模式知道它,并且它会自动匹配。如果您曾经编写过 AST walkers 代码,您就会体会到这是多么方便。
匹配知道 AST 节点,因此知道精确位置(文件/行/列),这使得生成具有精确位置信息的报告变得容易。
您需要向 DMS 添加自定义例程“inherits_from_STD_exception”,以验证传递给该例程的标识符树节点是(如 OP 所需)从 std::exception 派生的类。这需要在符号表中找到“std::exception”,并验证标识符树节点的符号表条目是一个类声明,并且从其他类声明传递继承(通过遵循符号表链接),直到 std::exception找到符号表条目。
DMS 转换规则是一对模式,本质上是“如果您看到这个,则将其替换为那个”。
我们使用 DMS 为 COBOL 和 C++ 构建了多个自定义样式检查器。这仍然是相当大量的工作,主要是因为 C++ 是一种相当复杂的语言,您必须仔细考虑检查的确切含义。
更棘手的检查和那些开始陷入深度静态分析的测试需要访问控制和数据流信息。DMS 现在计算 C++ 的控制流,我们正在致力于数据流分析(我们已经为 Java、IBM Enterprise COBOL 和各种 C 方言完成了这项工作)。分析结果被绑定回 AST 节点,以便可以使用模式来查找样式检查的元素,然后按照数据流将元素绑定在一起(如果需要)。
总而言之,使用 DMS(或者实际上使用任何其他以半准确方式处理 C++ 的工具),编写额外或复杂的样式检查不太可能“方便”。你应该希望“有良好的技术背景”。