C/C++ 静态代码检测工具 Clang-tidy 简易教程:安装、使用、配合 cmake 使用、加入 CI/CD 流程等


前言

最近尝试将 Clang-tidy 加入到项目中,以便加强代码规范。本文纪录在这一过程中积累的关于 Clang-tidy 的知识,涵盖安装、使用、cmake 和 CI/CD 实践等内容。

Clang-tidy 是什么?

Clang-tidy是一个强大的开源工具,用于自动执行C++源代码的静态分析。它是Clang项目的一部分,可以检查代码中的错误、风格问题、性能问题等,并且可以自动修复一些常见问题。

Clang-tidy的一个重要特性是它的可扩展性。你可以编写自己的检查器来检查特定的编程错误或风格问题。这使得Clang-tidy成为一个非常强大的工具,可以定制以适应你的编程风格和需求。

Clang-tidy还可以与其他工具集成,如编辑器和持续集成系统,以自动执行代码检查和修复。这使得它成为提高代码质量和减少编程错误的重要工具。

Clang-tidy 检测流程

Clang-tidy检测一个文件的过程大致如下:

  1. 预处理:Clang-tidy首先对源代码进行预处理,解析出宏定义、头文件包含等信息。
  2. 语法分析:然后,Clang-tidy对预处理后的代码进行语法分析,生成抽象语法树(AST)。
  3. 静态分析:接着,Clang-tidy对AST进行静态分析,检查可能的编程错误、风格问题等。
  4. 报告和修复:最后,Clang-tidy报告检测到的问题,并尝试自动修复一些问题。

安装

Macos
推荐使用 pip 进行安装:

pip install clang-tidy

Linux,使用 apt 或者 pip

apt-get install clang-tidy
pip install clang-tidy

使用

Clang-tidy 命令

使用 clang-tidy 检测某个文件时需要知道头文件路径等编译信息,这是因为它需要了解代码的上下文环境,以便正确地解析代码并检测潜在的问题。例如,如果代码中使用了某个库的函数,Clang-tidy需要知道该库的头文件路径才能正确地解析函数的声明和定义。如果缺少这些编译信息,Clang-tidy可能会产生误报或漏报的问题。

因此一个 clang-tidy 检查的命令大概是这样的:

clang-tidy --checks='-*' test.cpp -- -I ./src/ -x c++

这个命令的含义如下:

  1. clang-tidy:调用clang-tidy工具。
  2. –checks=‘-*’:指定要执行的检查器。这里的-*表示不执行任何检查器,即关闭所有检查器。
  3. test.cpp:指定要检查的源文件名。
  4. –:用于分隔clang-tidy的选项和编译器的选项。
  5. -I ./src/:指定编译器的头文件搜索路径,即将./src/目录添加到头文件搜索路径中。
  6. -x c++:指定编译器要编译的源文件类型为C++。

在实际使用 clang-tidy 中,搭配上 cmake 将事半功倍。clang-tidy 中有个 -p 参数,-p build-path参数是用于指定Clang-tidy读取编译命令数据库的路径。编译命令数据库通常是一个名为compile_commands.json的文件,它包含了编译每个源文件所需的完整命令行,包括编译器选项、头文件路径等信息。

例如,如果你使用CMake进行构建,可以通过设置-DCMAKE_EXPORT_COMPILE_COMMANDS=ON选项来生成compile_commands.json文件。然后,你可以使用-p参数告诉Clang-tidy在哪里找到这个文件,如clang-tidy -p /path/to/build/directory。

如果没有指定-p参数,Clang-tidy会尝试在第一个输入文件的所有父路径中搜compile_commands.json文件。

这个参数的主要目的是让Clang-tidy能够正确解析源代码。因为C++的语法和语义很大程度上取决于编译上下文,如果没有正确的编译信息,Clang-tidy可能无法正确解析源代码,也就无法进行准确的静态分析。

clang-tidy-diff.py 脚本

clang 官方提供了一些脚本,在 clang-tidy 命令行的基础上提供了额外的能力,通过 clang-tidy-diff.py 来检查 git diff 中实际发生修改的代码。你可以这样使用:

git diff branchA master | python clang-tidy.py -path /path/to/compile_commands.json

这个命令的含义如下:

  1. git diff branchA master:使用git diff命令比较分支branchA和master之间的差异,并将差异输出到标准输出流中。
  2. |:管道符号,将git diff的输出作为下一个命令的输入。
  3. python clang-tidy-diff.py:使用Python脚本 clang-tidy-diff.py 来处理git diff的输出。
  4. -path /path/to/compile_commands.json:指定编译命令数据库的路径为/path/to/compile_commands.json。

实践

检查项

clang-tidy 的检查项非常丰富,这部分可以参考:

如果你不知道检查哪些,那偷个懒直接使用 CLion 的默认配置即可,通过 how-to-dump-clions-default-clang-tidy-configuration 提到的方法生成 .clang-tidy 文件。命令行中使用 --config-file 指定 .clang-tidy 文件即可

clang-tidy --config-file=/path/to/.clang-tidy ...

在检查项中,可以指定 warning-as-error ,例如

---
Checks: '-*,
bugprone-argument-comment,
bugprone-assert-side-effect,
bugprone-bad-signal-to-kill-thread,
bugprone-copy-constructor-init'
WarningsAsErrors: 'bugprone-*'

这样检测到相关问题后直接报错,方便 ci/cd 进行检查

编译中开启 clang-tidy 检查

在CMake中,我们可以通过设置CMAKE_CXX_CLANG_TIDY属性来为整个项目开启Clang-tidy检查。例如:

set(CLANG_TIDY_COMMAND clang-tidy;
            -header-filter=^${PROJECT_ROOT_DIR}/src/.*;
            -config-file=${PROJECT_ROOT_DIR}/.clang-tidy;)
set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_COMMAND})

在这个例子中,CMAKE_CXX_CLANG_TIDY被设置为一个包含Clang-tidy命令和相关选项的变量。这将导致所有编译的文件都会被Clang-tidy检查。

然而,有时我们可能希望跳过某些文件的检查,比如第三方库的代码。在这种情况下,使用CMAKE_CXX_CLANG_TIDY就不再适用。一个更好的选择是使用set_target_properties命令,只为特定的目标开启Clang-tidy检查。例如:

set_target_properties(myTarget
            PROPERTIES
            CXX_CLANG_TIDY ${CLANG_TIDY_COMMAND})

在这个例子中,只有myTarget目标的源文件会被Clang-tidy检查,其他没有设置CXX_CLANG_TIDY属性的目标则不会受到影响。

跳过部分代码的检查

可以使用 NOLINTNOLINTNEXTLINE 以及 NOLINTBEGIN + NOLINTEND 跳过某些代码的静态检测

int a = 10; // NOLINT

// NOLINTNEXTLINE
int b = 20;

// NOLINTBEGIN
void fun()
{
	dosomething(); // 跳过 BEGIN 和 END 之间的代码
}
// NOLINTEND 

将 clang-tidy 加入 CI/CD

有两种方式将 clang-tidy 加入 CI/CD 中:

  1. 在 cmake 中设置 CXX_CLANG_TIDY 开启编译时检查
  2. 使用 clang-tidy-diff.py 检测被修改的文件

对比两种方式各有优劣:

  1. 开启编译时检查,设置方便,但增加编译耗时,且对现有所有文件进行检查,如果项目文件很多那么修改所有 error 是一个苦力活。
  2. clang-tidy-diff.py 只检查增量或者被修改的文件,但需要提供 compile_commands.json 文件。

最终选择了方法 2,庆幸的是目前大部分 C/C++ 项目都使用基本使用 cmake 来管理,包括 Android NDK,因此使用起来并无太大麻烦。例如你想在 CI/CD 中检查某个 Android NDK 项目,你可以这么做:

  1. 使用 ./gradlew assembleFullRelease 编译你的项目
  2. 找到生成的 compile_commands.json 文件所在
  3. 使用 clang-tidy-diff.py 检查被修改的文件:git diff A B | python clang-tidy-diff.py -path=/path/compile_commands.json -config-file=/path/.clang-tidy

参考

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值