参与贡献¶
clang-tidy 拥有若干自有检查,并可以运行 Clang 静态分析器检查,但其强大之处在于能够轻松编写自定义检查。
检查被组织成模块,这些模块可以链接到 clang-tidy 中,而无需或只需对 clang-tidy 中的代码进行少量更改。
检查可以使用 PPCallbacks 在预处理器级别或使用 AST Matchers 在 AST 级别接入分析。当发现错误时,检查可以以类似于 Clang 诊断工作的方式报告它们。修复建议可以附加到诊断消息。
clang-tidy 提供的接口使得只需几行代码即可轻松编写有用且精确的检查。如果你有编写良好检查的想法,本文档的其余部分将介绍如何做到这一点。
- 在开发 clang-tidy 检查时,有一些工具特别有用
add_new_check.py
是一个用于自动添加新检查的脚本,它将创建检查、更新 CMake 文件并创建测试;rename_check.py
如脚本名称所暗示的那样,重命名现有检查;pp-trace 记录 PPCallbacks 上的源文件方法调用,对于理解预处理器机制非常有价值;
clang-query 对于交互式原型化 AST 匹配器和探索 Clang AST 非常有价值;
clang-check 与
-ast-dump
(以及可选的-ast-dump-filter
)一起提供了便捷的方式来转储 C++ 程序的 AST。
如果 CMake 使用 CLANG_TIDY_ENABLE_STATIC_ANALYZER=NO
进行配置,则 clang-tidy 将不会构建对 clang-analyzer-*
检查或 mpi-*
检查的支持。
选择检查的正确位置¶
如果你有编写检查的想法,你应该确定它应该实现为
Clang 诊断:如果检查足够通用,目标代码模式最有可能存在错误(而不是风格或可读性问题),可以有效地实现并且具有极低的误报率,那么它可能是一个良好的 Clang 诊断。
Clang 静态分析器检查:如果检查需要某种控制流分析,那么它应该实现为静态分析器检查。
clang-tidy 检查 非常适合 linter 风格的检查、与特定编码风格相关的检查、解决代码可读性的检查等。
准备工作区¶
如果你不熟悉 LLVM 开发,你应该阅读 LLVM 系统入门、使用 Clang 工具 和 如何为 LLVM 设置 Clang 工具 文档以使用 CMake 检查和构建 LLVM、Clang 和 Clang Extra Tools。
完成后,切换到 llvm/clang-tools-extra
目录,让我们开始吧!
当您 配置 CMake 构建 时,请确保您启用了 clang
和 clang-tools-extra
项目来构建 clang-tidy。由于您的新检查将具有相关的文档,因此您还需要安装 Sphinx 并在 CMake 配置中启用它。为了节省核心 Clang 库的构建时间,您可能只希望在 CMake 配置中启用 X86
目标。
目录结构¶
clang-tidy 源代码位于 llvm/clang-tools-extra
目录中,其结构如下
clang-tidy/ # Clang-tidy core.
|-- ClangTidy.h # Interfaces for users.
|-- ClangTidyCheck.h # Interfaces for checks.
|-- ClangTidyModule.h # Interface for clang-tidy modules.
|-- ClangTidyModuleRegistry.h # Interface for registering of modules.
...
|-- google/ # Google clang-tidy module.
|-+
|-- GoogleTidyModule.cpp
|-- GoogleTidyModule.h
...
|-- llvm/ # LLVM clang-tidy module.
|-+
|-- LLVMTidyModule.cpp
|-- LLVMTidyModule.h
...
|-- objc/ # Objective-C clang-tidy module.
|-+
|-- ObjCTidyModule.cpp
|-- ObjCTidyModule.h
...
|-- tool/ # Sources of the clang-tidy binary.
...
test/clang-tidy/ # Integration tests.
...
unittests/clang-tidy/ # Unit tests.
|-- ClangTidyTest.h
|-- GoogleModuleTest.cpp
|-- LLVMModuleTest.cpp
|-- ObjCModuleTest.cpp
...
编写 clang-tidy 检查¶
因此你有一个关于 clang-tidy 有用检查的想法。
首先,如果你不熟悉 LLVM 开发,请阅读 LLVM 系统入门 文档,了解有关设置工作流程的说明,以及 LLVM 编码标准 文档,以熟悉项目中使用的编码风格。对于代码审查,我们目前使用 LLVM Github,尽管历史上我们使用过 Phabricator。
接下来,你需要确定检查属于哪个模块。模块位于 clang-tidy/ 的子目录中,包含针对代码质量的特定方面(性能、可读性等)、特定编码风格或标准(Google、LLVM、CERT 等)或广泛使用的 API(例如 MPI)的检查。它们的名称与用户界面中上面描述的检查组名称相同 above。
在选择模块和检查名称后,运行 clang-tidy/add_new_check.py
脚本创建检查的骨架并将其插入 clang-tidy。这是添加新检查的推荐方法。
如果我们想创建一个 readability-awesome-function-names,我们会运行
$ clang-tidy/add_new_check.py readability awesome-function-names
add_new_check.py
脚本将在指定模块目录中创建检查的类并将其注册到模块和构建系统中;
在
test/clang-tidy/
目录中创建一个 lit 测试文件;创建一个文档文件并将其包含到
docs/clang-tidy/checks/list.rst
中。
让我们更详细地看一下检查类定义
...
#include "../ClangTidyCheck.h"
namespace clang {
namespace tidy {
namespace readability {
...
class AwesomeFunctionNamesCheck : public ClangTidyCheck {
public:
AwesomeFunctionNamesCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
};
} // namespace readability
} // namespace tidy
} // namespace clang
...
检查的构造函数接收 Name
和 Context
参数,并且必须将它们转发到 ClangTidyCheck
构造函数。
在我们的案例中,检查需要在 AST 级别进行操作,并且它覆盖了 registerMatchers
和 check
方法。如果我们想在预处理器级别分析代码,则需要覆盖 registerPPCallbacks
方法。
在 registerMatchers
方法中,我们创建了一个 AST 匹配器(有关更多信息,请参见 AST Matchers),它将找到我们要检查的 AST 中的模式。匹配结果将传递给 check
方法,该方法可以进一步检查它们并报告诊断。
using namespace ast_matchers;
void AwesomeFunctionNamesCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(functionDecl().bind("x"), this);
}
void AwesomeFunctionNamesCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
if (!MatchedDecl->getIdentifier() || MatchedDecl->getName().startswith("awesome_"))
return;
diag(MatchedDecl->getLocation(), "function %0 is insufficiently awesome")
<< MatchedDecl
<< FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
}
(如果你想查看一个有用检查的示例,请查看 clang-tidy/google/ExplicitConstructorCheck.h 和 clang-tidy/google/ExplicitConstructorCheck.cpp)。
如果你需要与宏或预处理器指令进行交互,你将需要覆盖 registerPPCallbacks
方法。 add_new_check.py
脚本不会在你的新检查的起点中生成对该方法的覆盖。
如果你的检查仅在特定语言选项集中适用,请确保覆盖 isLanguageVersionSupported
方法以反映这一点。
检查开发技巧¶
编写你的第一个检查可能是一项艰巨的任务,尤其是在你不熟悉 LLVM 和 Clang 代码库的情况下。以下是一些关于如何在代码库中定位自己并增量式地处理检查的建议。
有用文档指南¶
LLVM 创建的许多支持类都被 Clang 使用,例如 StringRef 和 SmallVector。这些和其他常用类在 重要且有用的 LLVM API 和 为任务选择正确的数据结构 部分中进行了描述 LLVM 程序员手册。你不需要记住所有这些类的细节;生成的 doxygen 文档 包含了你需要的全部内容。在 LLVM/ADT/STLExtras.h 标头中,你会发现 STL 算法的有用版本,它们操作 LLVM 容器,例如 llvm::all_of.
Clang 在 LLVM 之上实现,并引入了你将在编写检查时与之交互的自己的类集。当检查发出诊断和修复建议时,这些与源代码中的位置相关联。源代码位置、源文件、源代码位置范围和 SourceManager 类提供了描述此类位置的机制。这些和其他主题在 “Clang” CFE 内部手册 中有描述。doxygen 生成的文档用作 Clang 内部的参考,而本文档用作其他开发者的指南。该手册中与检查开发人员相关的主题包括
Clang “基本” 库,其中包含有关诊断、修复建议和源代码位置的信息。
词法分析器和预处理器库,其中包含有关标记、词法分析(将字符转换为标记)和预处理器的信息。
AST 库,其中包含有关如何将 C++ 源代码语句表示为抽象语法树 (AST) 的信息。
大多数检查将通过 AST 与 C++ 源代码交互。一些检查将与预处理器交互。输入源文件将被词法分析和预处理,然后解析成 AST。一旦 AST 完全构建,检查就会通过将检查注册的 AST 匹配器应用于 AST 并使用来自 AST 的匹配节点集调用检查来运行。监视预处理器的操作与 AST 构建分离,但检查可以在预处理期间收集信息,以便在 AST 匹配节点时供检查以后使用。
C++ 源代码的每个语法(有时还有语义)元素都由 AST 中的不同类表示。您可以通过组合 AST 匹配器函数来选择您感兴趣的 AST 部分。您需要仔细研究 AST 匹配器参考 以了解不同匹配器函数之间的关系。
使用 Transformer 库¶
Transformer 库允许您编写一个检查,该检查通过将转换表示为 RewriteRule
来转换源代码。Transformer 库提供了用于组合对源代码的编辑以创建重写规则的函数。除非您需要执行低级源代码位置操作,否则您可能需要考虑使用 Transformer 库编写您的检查。 Clang Transformer 教程 详细介绍了 Transformer 库。
要使用 Transformer 库,请对 add_new_check.py
脚本生成的代码进行以下更改
包含
../utils/TransformerClangTidyCheck.h
而不是../ClangTidyCheck.h
将您的检查的基类从
ClangTidyCheck
更改为TransformerClangTidyCheck
删除您检查类中
registerMatchers
和check
方法的重写。编写一个函数来创建您的检查的
RewriteRule
。在您的检查的构造函数中调用该函数,以将重写规则传递给
TransformerClangTidyCheck
的构造函数。
增量开发您的检查¶
开发检查的最佳方法是从简单的测试用例开始,并逐步增加复杂度。 add_new_check.py
脚本创建的测试文件是您测试用例的起点。该过程的大致轮廓如下所示
为您的检查编写一个测试用例。
使用 clang-query 在测试文件上对匹配器进行原型设计。
在
registerMatchers
方法中捕获工作匹配器。在
check
方法中发出必要的诊断和修复。将必要的
CHECK-MESSAGES
和CHECK-FIXES
注释添加到您的测试用例中,以验证诊断和修复。构建目标
check-clang-tool
以确认测试通过。重复此过程,直到您检查的所有方面都包含在测试中。
对匹配器进行原型设计的最快方法是使用 clang-query 交互式地构建匹配器。对于复杂的匹配器,请增量地构建匹配表达式并使用 clang-query 的 let
命令保存命名匹配表达式以简化匹配器。
clang-query> let c1 cxxRecordDecl()
clang-query> match c1
或者,在先前匹配器的左括号后按 Tab 键也会显示哪些匹配器可以与先前匹配器链接,尽管某些可工作的匹配器可能未列出。请注意,Tab 补全目前在 Windows 上不起作用。
就像将一个巨大的函数分解成具有意图揭示名称的较小块可以帮助您理解一个复杂的算法一样,将一个匹配器分解成具有意图揭示名称的较小匹配器可以帮助您理解一个复杂的匹配器。
一旦您有了可工作的 clang-query 匹配器,C++ API 匹配器将与您交互式构建的匹配器相同或类似(在某些情况下,它们可能略有不同)。您可以使用局部变量来保留您应用于嵌套匹配器的意图揭示名称。
创建私有匹配器¶
有时您想匹配 AST 的特定方面,而这些方面没有通过现有 AST 匹配器提供。您可以使用与公共匹配器相同的基础设施创建自己的私有匹配器。私有匹配器可以通过消除对匹配节点进行复杂的手工制作的 AST 遍历来简化您 check
方法中的处理。使用私有匹配器允许您直接在匹配器中选择所需的 AST 部分,并在 check
方法中通过绑定名称引用它。
单元测试辅助代码¶
私有自定义匹配器是您检查的辅助支持代码的一个很好的例子,可以使用单元测试对其进行测试。通过编写单元测试而不是编写 FileCheck
集成测试,测试匹配器或其他支持类将更容易。 ASTMatchersTests
目标包含公共 AST 匹配器类的单元测试,并且是匹配器测试惯用法的一个很好的来源。
您可以通过构建 ClangTidyTests
目标来构建 Clang-tidy 单元测试。LLVM 和 Clang 中的测试目标从基于 IDE 的 CMake 生成器的“构建全部”样式操作中排除,因此您需要显式构建该目标才能构建单元测试。
使您的检查更加健壮¶
一旦您使用基本的“正常路径”场景覆盖了您的检查,您便需要用尽可能多的边缘情况来折磨您的检查,以确保您的检查是健壮的。在大型代码库(如 Clang/LLVM)上运行您的检查是捕获您在匹配器中没有考虑到的内容的一个好方法。但是,LLVM 代码库可能不足以用于测试目的,因为它是在特定编码风格和质量指标集下开发的。检查测试的代码库越大,社区对检查的有效性和误报率的信心就越高。
确保您的检查健壮的一些建议
创建包含您的检查匹配的代码的头文件。
使用 clang-tidy 验证修复是否正确应用于测试头文件。您需要手动执行此测试,直到在
check_clang_tidy.py
脚本中添加了用于检查消息和修复的自动支持。定义包含您的检查匹配的代码的宏。
定义包含您的检查匹配的代码的模板类。
定义包含您的检查匹配的代码的模板特化。
在 Windows 和 Linux 环境下测试您的检查。
注意高误报率。理想情况下,检查不会出现误报,但考虑到与 AST 的匹配不敏感于控制或数据流,因此预计会产生一定数量的误报。误报率越高,检查在实践中被采用的可能性就越低。应该制定机制来帮助用户管理误报。
有两种主要的误报管理机制:支持一种代码模式,允许程序员以一种临时的方式静默诊断,以及检查配置选项以控制检查的行为。
考虑支持一种代码模式,允许程序员在这样的代码模式可以清楚地表达程序员的意图时始终静默诊断。例如,允许显式转换为
void
来静默未使用的变量诊断。考虑添加检查配置选项,以允许用户选择更激进的检查行为,而不会给用户带来负担,因为这些选项对于常见的高置信度用例来说很方便。
记录您的检查¶
add_new_check.py
脚本在 发行说明、检查列表和检查文档本身的新文件中创建条目。建议您在检查类头文件中的 doxygen 注释中的第一句话以及检查文档的第一句话中使用一个简短的句子来总结您的检查做了什么,这些句子在发行说明中重复出现。在检查总结和检查文档中避免使用短语“此检查”。
如果您的检查与已发布的编码指南(C++ 核心指南、MISRA 等)或风格指南相关,请在您的检查文档中提供指向相关指南或风格指南部分的链接。
提供检查提供的诊断和修复的足够示例,以便用户可以轻松地理解在运行检查时其代码将发生什么变化。如果有例外或限制您的检查,请彻底记录它们。这将帮助用户了解检查提供的诊断和修复的范围。
构建目标 docs-clang-tools-html
将运行 Sphinx 文档生成器并在构建树的 tools/clang/tools/extra/docs/html 目录中创建文档 HTML 文件。确保您的检查在发行说明和检查列表中正确显示。确保您的检查文档的格式和结构看起来正确。
注册您的检查¶
( add_new_check.py
脚本负责在现有模块中注册检查。如果您想创建一个新模块或了解详细信息,请继续阅读。)
检查应该在相应的模块中使用不同的名称注册
class MyModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<ExplicitConstructorCheck>(
"my-explicit-constructor");
}
};
现在我们需要使用静态初始化的变量在 ClangTidyModuleRegistry
中注册该模块
static ClangTidyModuleRegistry::Add<MyModule> X("my-module",
"Adds my lint checks.");
在使用 LLVM 构建系统时,我们需要使用以下技巧来确保该模块链接到 clang-tidy 二进制文件
将此添加到 ClangTidyModuleRegistry::Add<MyModule>
变量附近
// This anchor is used to force the linker to link in the generated object file
// and thus register the MyModule.
volatile int MyModuleAnchorSource = 0;
并将此添加到 clang-tidy 二进制文件(或您将 clang-tidy
库链接到的二进制文件)的主要翻译单元 clang-tidy/ClangTidyForceLinker.h
中
// This anchor is used to force the linker to link the MyModule.
extern volatile int MyModuleAnchorSource;
static int MyModuleAnchorDestination = MyModuleAnchorSource;
配置检查¶
如果检查需要配置选项,它可以使用 Options.get<Type>("SomeOption", DefaultValue)
在检查构造函数中调用来访问检查特定的选项。在这种情况下,检查也应该覆盖 ClangTidyCheck::storeOptions
方法,以使检查提供的选项可被发现。此方法可以让 clang-tidy 知道检查实现了哪些选项以及当前值是什么(例如,对于 -dump-config
命令行选项)。
class MyCheck : public ClangTidyCheck {
const unsigned SomeOption1;
const std::string SomeOption2;
public:
MyCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
SomeOption(Options.get("SomeOption1", -1U)),
SomeOption(Options.get("SomeOption2", "some default")) {}
void storeOptions(ClangTidyOptions::OptionMap &Opts) override {
Options.store(Opts, "SomeOption1", SomeOption1);
Options.store(Opts, "SomeOption2", SomeOption2);
}
...
假设检查已使用名称“my-check”注册,则可以在 .clang-tidy
文件中以以下方式设置选项
CheckOptions:
my-check.SomeOption1: 123
my-check.SomeOption2: 'some other value'
如果你需要在命令行上指定检查选项,可以使用内联 YAML 格式
$ clang-tidy -config="{CheckOptions: {a: b, x: y}}" ...
测试检查¶
要运行 clang-tidy 的测试,请构建 check-clang-tools
目标。例如,如果你使用 ninja 项目生成器配置了你的 CMake 构建,请使用以下命令
$ ninja check-clang-tools
clang-tidy 检查可以使用单元测试或 lit 测试来测试。单元测试可能更方便用来测试具有严格检查的复杂替换。 Lit 测试允许使用部分文本匹配和正则表达式,这使得它们更适合编写诊断消息的简洁测试。
check_clang_tidy.py
脚本提供了一种简单的方法来测试诊断消息和修复。它从测试文件中过滤掉 CHECK
行,运行 clang-tidy 并使用两个独立的 FileCheck 调用来验证消息和修复:一次使用 FileCheck 的指令前缀设置为 CHECK-MESSAGES
,验证诊断消息,另一次使用指令前缀设置为 CHECK-FIXES
,对已修复的代码运行(即,在应用生成的修复后生成的代码)。特别是, CHECK-FIXES:
可用于检查代码是否未被修复修改,方法是检查它是否未更改地存在于修复的代码中。完整的 FileCheck 指令集可用(例如, CHECK-MESSAGES-SAME:
, CHECK-MESSAGES-NOT:
),但通常基本的 CHECK
形式(CHECK-MESSAGES
和 CHECK-FIXES
)足以用于 clang-tidy 测试。请注意,FileCheck 文档主要假设默认前缀(CHECK
),因此将指令描述为 CHECK:
, CHECK-SAME:
, CHECK-NOT:
等等。对于 clang-tidy 测试,将 CHECK
替换为 CHECK-FIXES
或 CHECK-MESSAGES
。
check_clang_tidy.py
启用的一项额外检查确保,如果 CHECK-MESSAGES: 用于某个文件,则每个警告或错误都必须在该文件中具有关联的 CHECK。或者,如果你还想要确保所有注释都得到检查,可以使用 CHECK-NOTES:
。
要使用 check_clang_tidy.py
脚本,请将具有适当 RUN
行的 .cpp 文件放在 test/clang-tidy
目录中。使用 CHECK-MESSAGES:
和 CHECK-FIXES:
行来编写针对诊断消息和修复代码的检查。
建议使检查尽可能具体,以避免检查匹配输入的错误部分。在测试代码中使用 [[@LINE+X]]
/[[@LINE-X]]
替换和不同的函数和变量名。
以下是用 check_clang_tidy.py
脚本进行测试的示例(完整的源代码位于 test/clang-tidy/checkers/google/readability-casting.cpp)。
// RUN: %check_clang_tidy %s google-readability-casting %t
void f(int a) {
int b = (int)a;
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant cast to the same type [google-readability-casting]
// CHECK-FIXES: int b = a;
}
要在同一个测试文件中检查多个场景,请在 check_clang_tidy.py
命令行上使用 -check-suffix=SUFFIX-NAME
或 -check-suffixes=SUFFIX-NAME-1,SUFFIX-NAME-2,...
。使用 -check-suffix[es]=SUFFIX-NAME
,你需要将你的 CHECK-*
指令替换为 CHECK-MESSAGES-SUFFIX-NAME
和 CHECK-FIXES-SUFFIX-NAME
。
以下是一个示例
// RUN: %check_clang_tidy -check-suffix=USING-A %s misc-unused-using-decls %t -- -- -DUSING_A
// RUN: %check_clang_tidy -check-suffix=USING-B %s misc-unused-using-decls %t -- -- -DUSING_B
// RUN: %check_clang_tidy %s misc-unused-using-decls %t
...
// CHECK-MESSAGES-USING-A: :[[@LINE-8]]:10: warning: using decl 'A' {{.*}}
// CHECK-MESSAGES-USING-B: :[[@LINE-7]]:10: warning: using decl 'B' {{.*}}
// CHECK-MESSAGES: :[[@LINE-6]]:10: warning: using decl 'C' {{.*}}
// CHECK-FIXES-USING-A-NOT: using a::A;$
// CHECK-FIXES-USING-B-NOT: using a::B;$
// CHECK-FIXES-NOT: using a::C;$
C++ 语言中有许多不为人知的角落,使你的检查在所有情况下都完美地工作可能很困难,尤其是在它发出修复提示的情况下。最常见的问题是宏和模板
在宏体/模板定义中编写的代码可能有不同的含义,具体取决于宏展开/模板实例化;
多个宏展开/模板实例化可能会导致同一代码被检查多次(可能具有不同的含义,请参见 1),并且检查可能会多次发出相同的警告(或稍有不同的警告); clang-tidy 会对_完全相同_的警告进行去重,但如果警告略有不同,所有警告都会显示给用户(并用于应用修复,如果有);
对宏体/模板定义进行替换对于某些宏展开/模板实例化来说可能没问题,但很容易破坏其他一些展开/实例化。
如果你需要多个文件来练习检查的所有方面,建议你将它们放在包含你检查的模块的 Inputs
目录下以检查命名的子目录中。这可以防止测试目录变得杂乱。
如果你需要验证你的检查如何与系统头文件交互,一组模拟的系统头文件位于 checkers/Inputs/Headers
目录中。此目录的路径在带有变量 %clang_tidy_headers
的 lit 测试中可用。
树外检查插件¶
开发树外检查作为插件,在很大程度上遵循上面概述的步骤,包括创建一个新模块并进行修改以注册该模块。该插件是一个共享库,其代码位于 clang-tidy 构建系统之外。针对 LLVM 构建和链接此共享库,就像针对其他类型的 Clang 插件一样。如果使用 CMake,在调用 add_library
或 llvm_add_library
时使用关键字 MODULE
。
可以通过将 -load 传递给 clang-tidy 以及要启用的检查的名称来加载插件。
$ clang-tidy --checks=-*,my-explicit-constructor -list-checks -load myplugin.so
对于 ABI 和 API 稳定性没有预期,因此插件必须针对将加载插件的 clang-tidy 版本进行编译。
插件可以使用线程、TLS 或任何其他可用于树内代码的设施,这些设施可从外部头文件中访问。
请注意,测试树外检查可能涉及从源代码编译的 LLVM 安装中获取 llvm-lit
。有关如何执行此操作的信息,请参见 LLVM 系统入门。
或者,按照 测试套件指南 获取 lit 并获取 FileCheck 二进制文件,然后编写适合你需要的 check_clang_tidy.py 版本。
在 LLVM 上运行 clang-tidy¶
要测试检查,最好在更大的代码库中尝试一下。LLVM 和 Clang 是自然的目标,因为你已经拥有了周围的源代码。使用编译命令数据库是运行 clang-tidy 的最便捷方式;CMake 可以自动生成一个,有关如何启用它的描述,请参见 如何为 LLVM 设置 Clang 工具。一旦 compile_commands.json
就位并且 clang-tidy 的工作版本位于 PATH
中,整个代码库可以使用 clang-tidy/tool/run-clang-tidy.py
进行分析。该脚本使用默认的检查集在编译命令数据库中的每个翻译单元上执行 clang-tidy,并显示结果警告和错误。该脚本提供多个配置标志。
可以使用
-checks
参数覆盖默认的检查集,该参数采用与 clang-tidy 相同的格式。例如,-checks=-*,modernize-use-override
仅运行modernize-use-override
检查。要限制检查的文件,你可以提供一个或多个正则表达式参数,文件名将与这些参数匹配。
run-clang-tidy.py clang-tidy/.*Check\.cpp
仅分析 clang-tidy 检查。可能还需要使用-header-filter
和-exclude-header-filter
标志来限制显示警告的标头文件。它们具有与相应的 clang-tidy 标志相同的行为。要应用建议的修复,可以将
-fix
作为参数传递。这将在临时目录中收集所有更改并应用它们。传递-format
将在更改的行上运行 clang-format。
关于检查分析¶
clang-tidy 可以收集每个检查的分析信息,并为每个处理的源文件(翻译单元)输出该信息。
要启用分析信息收集,请使用 -enable-check-profile
参数。计时将作为表格输出到 stderr
。示例输出
$ clang-tidy -enable-check-profile -checks=-*,readability-function-size source.cpp
===-------------------------------------------------------------------------===
clang-tidy checks profiling
===-------------------------------------------------------------------------===
Total Execution Time: 1.0282 seconds (1.0258 wall clock)
---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name ---
0.9136 (100.0%) 0.1146 (100.0%) 1.0282 (100.0%) 1.0258 (100.0%) readability-function-size
0.9136 (100.0%) 0.1146 (100.0%) 1.0282 (100.0%) 1.0258 (100.0%) Total
它还可以将这些数据存储为 JSON 文件以供进一步处理。示例输出
$ clang-tidy -enable-check-profile -store-check-profile=. -checks=-*,readability-function-size source.cpp
$ # Note that there won't be timings table printed to the console.
$ ls /tmp/out/
20180516161318717446360-source.cpp.json
$ cat 20180516161318717446360-source.cpp.json
{
"file": "/path/to/source.cpp",
"timestamp": "2018-05-16 16:13:18.717446360",
"profile": {
"time.clang-tidy.readability-function-size.wall": 1.0421266555786133e+00,
"time.clang-tidy.readability-function-size.user": 9.2088400000005421e-01,
"time.clang-tidy.readability-function-size.sys": 1.2418899999999974e-01
}
}
只有一个参数控制配置文件存储
-store-check-profile=<prefix>
默认情况下,报告以表格格式打印到 stderr。传递此选项时,这些每个 TU 的配置文件将改为存储为 JSON。如果前缀不是绝对路径,则认为它相对于你运行 clang-tidy 的目录。路径中的所有
.
和..
模式都将折叠,符号链接也将解析。例如:假设你有一个名为
example.cpp
的源文件,位于/source
目录中。仅使用输入文件名,不使用源文件的完整路径。此外,它还以当前时间戳为前缀。如果你指定
-store-check-profile=/tmp
,则配置文件将保存到/tmp/<ISO8601-like timestamp>-example.cpp.json
如果你从
/foo
目录中运行 clang-tidy,并指定-store-check-profile=.
,则配置文件仍将保存到/foo/<ISO8601-like timestamp>-example.cpp.json