为clang-tidy添加自定义check

1,444 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

clang-tidy 简介

clang-tidy是基于LLVM-Clang开发的一个能够对源代码进行静态检查的工具。通过这个列表clang.llvm.org/extra/clang…,我们能看到官方提供的各类check。比如,为了预防bug而检测明显的infinite loop除零行为

通过执行clang-tidy -list-checks -checks=*命令,我们可以查看当前可用的所有check。

而如果想要执行某个检查,那就执行 clang-tidy --checks='check名字' 被测文件

比如检测无限循环的情况,对应check名字为 bugprone-infinite-loop,待测文件内容为:

int main(){
    int i=1, j=0;
    while(i>0) j++;
}

检测效果为

可以看到,clang-tidy可以匹配到源代码中符合某个异常模式的代码段,然后将其用warning或者error的形式报告出来

LLVM工具链的编译

如果我们需要添加自己的check,显然就需要手动修改llvm中clang-tidy部分的代码,然后重新编译该工具。下面介绍利用llvm源码编译生成clang以及clang-tidy的整体流程

  1. 我们可以首先下载llvm-project的最新源码:(from github.com/llvm/llvm-p…)例如,可执行git clone github.com/llvm/llvm-p…
  2. 然后进入llvm-project目录,新建build目录并进入mkdir build && cd build
  3. 接下来执行:cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="X86" -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" ../llvm 通过cmake来生成编译所需的Makefile,这部分选项的含义可以查询llvm-project仓库中readme文件得知
  4. 然后,执行make -j N(分配的核心数量)来进行编译。然后就是漫长的编译时间,有条件的小伙伴尽量分配更多的内存和核心来提高编译速度以及避免因内存不足而编译失败
  5. 编译结束后,在build/bin/ 中会生成clang-tidy可执行程序,可以用用看

clang-tidy的源码结构

在llvm-project中,第一层子目录如下所示:

.

...

|-- clang

|-- clang-tools-extra

...

其中,clang-tidy部分代码位于clang-tools-extra中,之前的cmake命令需要显式地指定该编译目录,因为默认编译对象是不包括这一目录的。

进入clang-tools-extra后,是这样

clang-tools-extra/

...

|-- clang-query

|-- clang-tidy

...

再进入clang-tidy,

clang-tools-extra/clang-tidy/

|-- CMakeLists.txt*

|-- ClangTidy.cpp*

|-- ClangTidy.h*

|-- ClangTidyCheck.cpp*

|-- ClangTidyCheck.h*

|-- ClangTidyDiagnosticConsumer.cpp*

|-- ClangTidyDiagnosticConsumer.h*

|-- ClangTidyForceLinker.h*

|-- ClangTidyModule.cpp*

|-- ClangTidyModule.h*

|-- ClangTidyModuleRegistry.h*

|-- ClangTidyOptions.cpp*

|-- ClangTidyOptions.h*

|-- ClangTidyProfiling.cpp*

|-- ClangTidyProfiling.h*

|-- ExpandModularHeadersPPCallbacks.cpp*

|-- ExpandModularHeadersPPCallbacks.h*

|-- GlobList.cpp*

|-- GlobList.h*

|-- NoLintDirectiveHandler.cpp*

|-- NoLintDirectiveHandler.h*

|-- abseil/

|-- add_new_check.py*

|-- altera/

|-- android/

|-- boost/

|-- bugprone/

|-- cert/

|-- clang-tidy-config.h.cmake*

|-- concurrency/

|-- cppcoreguidelines/

|-- darwin/

|-- fuchsia/

|-- google/

|-- hicpp/

|-- linuxkernel/

|-- llvm/

|-- llvmlibc/

|-- misc/

|-- modernize/

|-- mpi/

|-- objc/

|-- openmp/

|-- performance/

|-- plugin/

|-- portability/

|-- readability/

|-- rename_check.py*

|-- tool/

|-- utils/

`-- zircon/

我们就是需要在此路径下,添加新的check的源码

check的添加方法

注册

在 /llvm-project/clang-tools-extra/clang-tidy/ 目录下,执行 add_new_check.py 所属类别 check名字

例如,我希望在bugprone分类中添加一个能够检测所有超过二级的指针声明,名为multi-pointer,那么我就在此目录下执行 ./add_new_check.py bugprone multi-pointer,就可以看到

在bugprone目录下,自动生成了MultiPointerCheck.h/cpp两个文件并填充了check的默认模板,我们只需要修改这两个文件,然后重复上文所述的编译过程,就可以用到自己的check啦

编写自定义check

自动生成的MultiPointerCheck.h/cpp内容分别为

默认生成的两个方法,registerMatcher负责为Finder指定需要寻找的matcher,然后如果找到,则会调用check方法来对结果进行处理。在当前模板代码中前者注册了一个,寻找所有函数声明的matcher,并且将找到的代表函数声明的functionDecl节点命名为x。在check中用x这个名字取出这个节点,并检测函数名的前缀是否为awesome_,如果不是,则会提示可以将函数名前加上这个前缀

这里需要查阅一些clang的文档,例如clang.llvm.org/docs/LibAST…clang.llvm.org/docs/LibAST…等。在clang中,ASTMatcher是一个广为使用的模块,需要认真学习规则方能掌握。如果你想测试自己写的matcher是否有效,可以借助clang-query工具,它是一个交互式程序,你可以输入自己写的matcher并实时查看匹配结果。用法也很简单,直接执行clang query 源文件即可。然后输入m 你的matcher ,就可以看到匹配情况

回到我们的例子,为了检测出超过二级引用的指针,我们建立如下matcher:

varDecl(hasType(pointerType(pointee(pointerType(pointee(pointerType()))))))

它匹配到的是 具有(指向(指向指针类型的类型)的类型)这样类型的一个变量声明

并修改MultiPointerCheck.cpp文件为

然后,回到build目录,重新编译,输入make -j 8。

待编译结束之后,我们就可以使用

bin/clang-tidy --checks='bugprone-multi-pointer' ../../test/testASTMatcher.cpp 来进行检测

可以得到效果: