Clang-tidy
前导信息
LLVM
LLVM(Low Level Virtual Machine)是一种工具链技术,是模块化和可复用编译器的集合,用它可以实现不同语言,并支持不同硬件平台。
上图中:
-
左:前端,用于实现不同类型的语言
-
中:通用优化器
-
右:后端,用于支持不同类型的硬件设备
Clang
Clang是LLVM项目的一个子项目,是基于LLVM架构的C/C++/Objective-C编译器前端。
Clang-tidy
Clang-Tidy 是一款基于 LLVM/Clang 框架的强大静态代码分析工具,专门用于检测 C++ 代码中的潜在问题和改进建议。它通过生成和解析抽象语法树(AST)深入理解代码结构和语义,从而提供高精度的检测结果,帮助开发者提升代码质量。
因为它基于AST,所以要比基于正则表达式的静态检查工具更为精准,但是带来的缺点就是要比基于正则表达式的静态检查工具慢一点。也是因为它基于AST,所以clang-tidy运行的时候需要知道编译命令。
说白了,就是可以检查编码规范,比如:
-
头文件要使用c++风格的不使用c风格的。
-
有隐式类型转换,可以使用clang-tidy来检测
-
使用nullptr而不是NULL
-
…
与cpplint等工具不同的是,clang-tidy不仅仅可以做静态检查,还可以做一些修复工作。
clang-tidy实现有100+个check,详见^[1]。根据check不同种类,分为如下几大类:
-
boost 检测boost库API使用问题
-
cert 检测CERT的代码规范
-
cpp-core-guidelines 检测是否违反cpp-core-guidelines
-
google 检测是否违反google code style
-
llvm 检测是否违反llvm code style
-
readability 检测代码上相关问题,但又不明确属于任何代码规范的
-
misc 其它一些零碎的check
-
mpi 检测MPI API问题
-
modernize 把C++03代码转换成C++11代码,使用C++11新特性
-
performance 检测performance相关问题
Clang-tidy检测一个文件的过程大致如下:
-
Step1 预处理:Clang-tidy首先对源代码进行预处理,解析出宏定义、头文件包含等信息;
-
Step2 语法分析:然后,Clang-tidy对预处理后的代码进行语法分析,生成AST;
-
Step3 静态分析:接着,Clang-tidy对AST进行静态分析,检查可能的编程错误、风格问题等;
-
Step4 报告和修复:最后,Clang-tidy报告检测到的问题,并尝试自动修复一些问题。
1 Clang-tidy配置
1.1 Visual Studio
Visual Studio 2019或更新的版本已经自带了Clang-tidy^[2],如果没有,打开Visual Studio Installer,选择“使用C++的桌面开发”,在右侧工具集中找到“适用于Windows的C++ Clang工具”并勾选,点击修改按钮确认:
安装完成以后,打开项目,右键项目选择“属性”,在Code Analysis中找到Clang-Tidy并启用:
1.2 Windows
Clang-tidy包含在LLVM中,要在Windows上使用clang-tidy,需要从llvm-project下载并安装LLVM。
安装完成后,clang-tidy可执行文件位于${INSTALL_DIR}\LLVM\bin\clang-tidy.exe
.
然后,从这里下载run-clang-tidy.py,用于快速调用检查工具。
1.3 Ubuntu
ubuntu 16.04默认安装的是3.8版本:
sudo apt install clang-tidy clang
ubuntu 16.04 apt最高可安装6.0版本:
sudo apt install clang-tidy-6.0 clang-6.0 clang-tools-6.0
这里以6.0为例,安装完成后,可执行文件位于/usr/bin/clang-tidy-6,且自带run-clang-tidy.py,位于/usr/bin/run-clang-tidy-6.0.py。
更多信息请参见[3]。
1.4 CMake
自3.7.2起,CMake已经集成了Clang-tidy,在CMakeLists.txt中进行如下设定即可启用:
set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*")
更多信息请参见[4]。
2 Clang-tidy使用
2.1 规则文件 .clang-tidy
自行创建一个名为.clang-tidy的文件,填入规则
2.2 编译命令 Compile Commands
由于clang-tidy基于AST进行分析检查,需要预先知道被检查代码的编译命令compile_commands.json。
2.2.1 通过Visual Studio(不推荐)
右键项目选择“分析和代码清理”,选择“运行代码分析”,即可完成生成${PROJECT_ROOT}/build/${PROJECT_NAME}.dir/Debug/${PROJECT_NAME}.ClangTidy/compile_commands.json
但实测下来发现这种方法生成的compile_commands.json不完整,具体原因有待进一步研究。
2.2.2 通过CMake
编译时加上-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
命令,但需要注意,该命令仅适用于Makefile和Ninja,不支持MSVC^[2]。
错误示例:
cmake -S . -B build -G "Visual Studio 16 2019" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
不会生成compile_commands.json。
正确示例:
cmake -S . -B build -G "Unix Makefiles" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
可以生成compile_commands.json,一般位于${PROJECT_ROOT}/build/compile_commands.json
。
2.3 运行代码检查
一般地,当clang-tidy安装完成后,可通过命令行调用检查,这里以ubuntu为例:
clang-tidy-6.0 --list-checks -checks='*'
但每次都要输一大堆参数,这里更推荐使用run-clang-tidy.py进行更灵活的配置与调度。
新建一个shell script:
#!/bin/sh
clang_bin_dir="/usr/bin"
clang_tidy_binary="${clang_bin_dir}/clang-tidy-6.0"
clang_apply_replacements="${clang_bin_dir}/clang-apply-replacements-6.0"
clang_tidy_python_script="./run-clang-tidy-6.0.py"
compile_commands="./compile_commands.json"
tmp_compile_commands="./tmp/compile_commands.json"
cat $compile_commands | jq -c '[.[] | select(.file | contains("extern") | not)]' > $tmp_compile_commands
python "$clang_tidy_python_script" \
-clang-tidy-binary "$clang_tidy_binary" \
-clang-apply-replacements "$clang_apply_replacements" \
-p "./tmp" \
-header-filter='^((?!/external/|/src/external/).)*$' \
-checks='-*,modernize-use-nullptr' \
-export-fixes ./summary.yml \
-fix
变量含义如下:
-
clang_bin_dir: 安装clang-tidy的目录
-
clang_tidy_binary:clang-tidy的可执行文件名称
-
clang_apply_replacements:clang-apply-replacements的可执行文件名称,与clang-tidy的可执行文件位于相同目录
-
clang_tidy_python_script:run-clang-tidy.py的路径
-
compile_commands:步骤2.2取得的compile_commands.json
-
tmp_compile_commands:对compile_commands.json进行后处理得到的compile_commands.json
这段script的作用是对compile_commands进行预处理,然后利用预处理的compile_commands调用clang-tidy检查。
clang-tidy基于compile_commands.json进行检查,但这个json中可能会包含项目之外的文件,例如第三方依赖或系统库,我们不能,也不希望去修改这些文件,因此需要对compile_commands.json进行修改,以排除这些文件:
cat $compile_commands | jq -c '[.[] | select(.file | contains("extern") | not)]' > $tmp_compile_commands
上面命令的作用是读取compile_commands,并filter掉extern文件夹,然后将新的编译命令保存至tmp_compile_commands。
同理,也可以指定想要分析的文件夹:
cat $compile_commands | jq -c '[.[] | select(.file | contains("my_proj") | not)]' > $tmp_compile_commands
这里就只会保留my_proj下代码的compile commands。
下面这段python调用是真正clang-tidy检查的部分:
python "$clang_tidy_python_script" \
-clang-tidy-binary "$clang_tidy_binary" \
-clang-apply-replacements "$clang_apply_replacements" \
-p "./tmp" \
-header-filter='^((?!/src/external/|/include/external/).)*$' \
-checks='-*,modernize-use-nullptr' \
-export-fixes ./summary.yml \
-fix
重要参数:
-p:compile_commands.json所在的目录
-header-filter:需要跳过检查文件的目录,按照正则表达式的形式填写
-checks:需要检查的规则,这里只检查了modernize-use-nullptr。或者不使用“-checks=”选项,而在项目主目录之下添加.clang-tidy文件,在里面编写项目的检查规则,这种方式更加适合对整个项目进行定制化的规则编写。.clang-tidy文件并不是必须放在主目录之下,只是通常放在主目录之下方便对整个项目进行检查。
-export-fixes:将检查到的问题保存到本地文件
-fix:尝试修复问题
References
- https://clang.llvm.org/extra/clang-tidy/checks/list.html
- https://learn.microsoft.com/zh-cn/cpp/code-quality/clang-tidy?view=msvc-160
- https://fekir.info/post/clang-tidy-windows/
- https://ortogonal.github.io/cmake-clang-tidy/
- https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html#cmake-export-compile-commands
- https://stackoverflow.com/questions/61001314/what-is-the-correct-way-of-providing-header-filter-for-clang-tidy-in-cmake