LLVM 之 Clang 源码分析篇(1):clang::ento::CheckerInfo 结构体
Author: stormQ
Created: Thursday, 08. April 2021 09:39PM
Last Modified: Wednesday, 14. April 2021 08:00PM
本文基于release/12.x
版本的 LLVM 源码,研究了clang::ento::CheckerInfo
结构体中各数据成员表示的意义以及成员函数的作用。从而,有助于理解 Clang 静态分析器源码实现的其他相关部分。
结构体clang::ento::CheckerInfo
的源码实现目录如下:
头文件为clang/include/clang/StaticAnalyzer/Core/CheckerRegistryData.h
源文件为clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp
step 1: 数据成员Initialize
、ShouldRegister
、FullName
和IsHidden
等
1) 数据成员Initialize
Initialize
是结构体CheckerInfo
的数据成员之一。其定义及初始值如下:
104 RegisterCheckerFn Initialize = nullptr;
查看数据类型RegisterCheckerFn
的定义:
39 using RegisterCheckerFn = void (*)(CheckerManager &);
因此,数据成员Initialize
是一个函数指针。其函数原型为void (*)(CheckerManager &);
。
2) 数据成员ShouldRegister
ShouldRegister
是结构体CheckerInfo
的数据成员之一。其定义及初始值如下:
105 ShouldRegisterFunction ShouldRegister = nullptr;
查看数据类型ShouldRegisterFunction
的定义:
40 using ShouldRegisterFunction = bool (*)(const CheckerManager &);
因此,数据成员ShouldRegister
是一个函数指针。其函数原型为bool (*)(const CheckerManager &);
。
3) 数据成员FullName
FullName
是结构体CheckerInfo
的数据成员之一。其定义如下:
106 StringRef FullName;
4) 数据成员Desc
Desc
是结构体CheckerInfo
的数据成员之一。其定义如下:
107 StringRef Desc;
5) 数据成员DocumentationUri
DocumentationUri
是结构体CheckerInfo
的数据成员之一。其定义如下:
108 StringRef DocumentationUri;
6) 数据成员IsHidden
IsHidden
是结构体CheckerInfo
的数据成员之一。其定义及初始值如下:
110 bool IsHidden = false;
那么,这些数据成员代表的意义分别是什么呢?
通过搜索源码发现,仅在CheckerInfo::CheckerInfo()
构造函数中会设置这些数据成员的值。其源码实现如下:
130 CheckerInfo(RegisterCheckerFn Fn, ShouldRegisterFunction sfn, StringRef Name,
131 StringRef Desc, StringRef DocsUri, bool IsHidden)
132 : Initialize(Fn), ShouldRegister(sfn), FullName(Name), Desc(Desc),
133 DocumentationUri(DocsUri), IsHidden(IsHidden) {}
而在CheckerRegistry::addChecker()
函数中会添加数据类型为CheckerInfo
结构体的元素。其源码实现如下(定义在 clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp 中):
435 void CheckerRegistry::addChecker(RegisterCheckerFn Rfn,
436 ShouldRegisterFunction Sfn, StringRef Name,
437 StringRef Desc, StringRef DocsUri,
438 bool IsHidden) {
439 Data.Checkers.emplace_back(Rfn, Sfn, Name, Desc, DocsUri, IsHidden);
// 省略 ...
448 }
通过 GDB 调试进入CheckerRegistry::addChecker()
函数后,打印其函数参数的值。这里仅展示部分检查器的打印结果,如下所示。
打印结果 1:
(gdb) i args
this = 0x7fffffffbb80
Rfn = 0x7fffe31e7fef <clang::ento::registerAnalysisOrderChecker(clang::ento::CheckerManager&)>
Sfn = 0x7fffe31e800e <clang::ento::shouldRegisterAnalysisOrderChecker(clang::ento::CheckerManager const&)>
Name = {static npos = 18446744073709551615, Data = 0x7fffe9feb8f9 "debug.AnalysisOrder", Length = 19}
Desc = {static npos = 18446744073709551615, Data = 0x7fffe9feb8c0 "Print callbacks that are called during analysis in order", Length = 56}
DocsUri = {static npos = 18446744073709551615, Data = 0x7fffe9feb8be "", Length = 0}
IsHidden = true
打印结果 2:
(gdb) i args
this = 0x7fffffffbb80
Rfn = 0x7fffe31ef975 <clang::ento::registerAnalyzerStatsChecker(clang::ento::CheckerManager&)>
Sfn = 0x7fffe31ef994 <clang::ento::shouldRegisterAnalyzerStatsChecker(clang::ento::CheckerManager const&)>
Name = {static npos = 18446744073709551615, Data = 0x7fffe9feb937 "debug.Stats", Length = 11}
Desc = {static npos = 18446744073709551615, Data = 0x7fffe9feb910 "Emit warnings with analyzer statistics", Length = 38}
DocsUri = {static npos = 18446744073709551615, Data = 0x7fffe9feb8be "", Length = 0}
IsHidden = true
打印结果 3:
(gdb) i args
this = 0x7fffffffbb80
Rfn = 0x7fffe31f2e10 <clang::ento::registerArrayBoundChecker(clang::ento::CheckerManager&)>
Sfn = 0x7fffe31f2e2f <clang::ento::shouldRegisterArrayBoundChecker(clang::ento::CheckerManager const&)>
Name = {static npos = 18446744073709551615, Data = 0x7fffe9feb9c4 "alpha.security.ArrayBound", Length = 25}
Desc = {static npos = 18446744073709551615, Data = 0x7fffe9feb998 "Warn about buffer overflows (older checker)", Length = 43}
DocsUri = {static npos = 18446744073709551615, Data = 0x7fffe9feb948 "https://clang-analyzer.llvm.org/alpha_checks.html#alpha.security.ArrayBound", Length = 75}
IsHidden = false
从上面的结果可以看出:
参数Rfn
,表示检查器注册函数registerXXX
的运行时内存地址。
参数Sfn
,表示检查器注册函数shouldRegisterXXX
的运行时内存地址。
参数Name
,表示检查器的全称(包括检查器所属包的包名)。
参数Desc
,表示检查器的描述。
参数DocsUri
,表示检查器文档的URI
地址。
参数IsHidden
,表示的意义见下文。
而这些参数的值正是文件Checkers.inc
中传递给CHECKER
宏定义的那些值,如下所示:
61 #ifdef GET_CHECKERS
62
63 CHECKER("debug.AnalysisOrder", AnalysisOrderChecker, "Print callbacks that are called during analysis in order", "", true)
64 CHECKER("debug.Stats", AnalyzerStatsChecker, "Emit warnings with analyzer statistics", "", true)
65 CHECKER("alpha.security.ArrayBound", ArrayBoundChecker, "Warn about buffer overflows (older checker)", "https://clang-analyzer.llvm.org/alpha_checks.html#alpha.security.ArrayBound", false)
// 省略 ...
249
250 #endif // GET_CHECKERS
Checkers.inc
文件是由工具TableGen
根据Checkers.td
文件的内容在编译 Clang 时生成的。编译完成后,该文件位于构建目录的子文件夹tools/clang/include/clang/StaticAnalyzer/Checkers/
中。
在文件Checkers.td
中,这些参数对应的内容如下:
// 省略 ...
20 def Alpha : Package<"alpha">;
// 省略 ...
71 def SecurityAlpha : Package<"security">, ParentPackage<Alpha>;
// 省略 ...
110 def Debug : Package<"debug">, Hidden;
// 省略 ...
944 let ParentPackage = SecurityAlpha in {
945
946 def ArrayBoundChecker : Checker<"ArrayBound">,
947 HelpText<"Warn about buffer overflows (older checker)">,
948 Documentation<HasAlphaDocumentation>;
// 省略 ...
982 } // end "alpha.security"
// 省略 ...
1323 let ParentPackage = Debug in {
1324
1325 def AnalysisOrderChecker : Checker<"AnalysisOrder">,
1326 HelpText<"Print callbacks that are called during analysis in order">,
// 省略 ...
1467 Documentation<NotDocumented>;
// 省略 ...
1517 def AnalyzerStatsChecker : Checker<"Stats">,
1518 HelpText<"Emit warnings with analyzer statistics">,
1519 Documentation<NotDocumented>;
// 省略 ...
1567 } // end "debug"
step 2: 数据成员State
State
是结构体CheckerInfo
的数据成员之一。其定义及初始值如下:
111 StateFromCmdLine State = StateFromCmdLine::State_Unspecified;
查看数据类型StateFromCmdLine
的定义:
95 enum class StateFromCmdLine {
96 // This checker wasn't explicitly enabled or disabled.
97 State_Unspecified,
98 // This checker was explicitly disabled.
99 State_Disabled,
100 // This checker was explicitly enabled.
101 State_Enabled
102 };
因此,数据成员State
的取值可能有三个,分别为:State_Unspecified
、State_Disabled
和State_Enabled
。
那么,这些值代表的意义分别是什么呢?
通过搜索源码发现,仅在CheckerRegistry::CheckerRegistry()
构造函数中会设置State
的值。其源码实现如下(定义在 clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp 中):
51 CheckerRegistry::CheckerRegistry(
52 CheckerRegistryData &Data, ArrayRef<std::string> Plugins,
53 DiagnosticsEngine &Diags, AnalyzerOptions &AnOpts,
54 ArrayRef<std::function<void(CheckerRegistry &)>> CheckerRegistrationFns)
55 : Data(Data), Diags(Diags), AnOpts(AnOpts) {
// 省略 ...
171 // Parse '-analyzer-checker' and '-analyzer-disable-checker' options from the
172 // command line.
173 for (const std::pair<std::string, bool> &Opt : AnOpts.CheckersAndPackages) {
174 CheckerInfoListRange CheckerForCmdLineArg =
175 Data.getMutableCheckersForCmdLineArg(Opt.first);
176
177 if (CheckerForCmdLineArg.begin() == CheckerForCmdLineArg.end()) {
178 Diags.Report(diag::err_unknown_analyzer_checker_or_package) << Opt.first;
179 Diags.Report(diag::note_suggest_disabling_all_checkers);
180 }
181
182 for (CheckerInfo &checker : CheckerForCmdLineArg) {
183 checker.State = Opt.second ? StateFromCmdLine::State_Enabled
184 : StateFromCmdLine::State_Disabled;
185 }
186 }
// 省略 ...
188 }
只指定-analyzer-checker=alpha.core.MainCall
标志时,调试数据成员State
的值被设置的过程。结果如下:
(gdb) n
183 checker.State = Opt.second ? StateFromCmdLine::State_Enabled
1: checker.FullName = {static npos = 18446744073709551615, Data = 0x7fffe9fee0bc "alpha.core.MainCall", Length = 19}
(gdb) p Opt
$1 = {first = "alpha.core.MainCall", second = true}
从上面的结果可以看出,通过-analyzer-checker
选项指定的检查器,其对应的CheckerInfo
结构体中数据成员State
的值会被设置为StateFromCmdLine::State_Enabled
。因此,StateFromCmdLine::State_Enabled
表示启用检查器。
只指定-analyzer-disable-checker=alpha.core.MainCall
标志时,调试数据成员State
的值被设置的过程。结果如下:
(gdb) n
183 checker.State = Opt.second ? StateFromCmdLine::State_Enabled
1: checker.FullName = {static npos = 18446744073709551615, Data = 0x7fffe9fee0bc "alpha.core.MainCall", Length = 19}
(gdb) p Opt
$1 = {first = "alpha.core.MainCall", second = false}
从上面的结果可以看出,通过-analyzer-disable-checker
选项指定的检查器,其对应的CheckerInfo
结构体中数据成员State
的值会被设置为StateFromCmdLine::State_Disabled
。因此,StateFromCmdLine::State_Disabled
表示禁用检查器。
同时指定-analyzer-checker=alpha.core.MainCall
和-analyzer-disable-checker=alpha.core.MainCall
标志时,调试数据成员State
的值被设置的过程。结果如下:
(gdb) p checker.FullName
$1 = {static npos = 18446744073709551615, Data = 0x7fffe9fee0bc "alpha.core.MainCall", Length = 19}
(gdb) p Opt
$2 = {first = "alpha.core.MainCall", second = true}
省略 ...
(gdb) p checker.FullName
$3 = {static npos = 18446744073709551615, Data = 0x7fffe9fee0bc "alpha.core.MainCall", Length = 19}
(gdb) p Opt
$4 = {first = "alpha.core.MainCall", second = false}
从上面的结果可以看出,检查器alpha.core.MainCall
对应的State
的值最终会被设置为StateFromCmdLine::State_Disabled
。
因此,对同一个检查器而言,如果该检查器在命令行中既被-analyzer-checker
标志指定,也被-analyzer-disable-checker
标志指定,那么最终是否启用该检查器取决于最后一个出现的标志是什么。
从上述分析可以得出,结构体CheckerInfo
中的数据成员State
的作用为:表示检查器的启用状态(是显式地启用了检查器,还是显式地禁用了检查器,还是未指定)。
step 3: 数据成员Dependencies
和WeakDependencies
1) 数据成员Dependencies
Dependencies
是结构体CheckerInfo
的数据成员之一。其定义如下:
113 ConstCheckerInfoList Dependencies;
查看数据类型ConstCheckerInfoList
的定义:
89 using ConstCheckerInfoList = llvm::SmallVector<const CheckerInfo *, 0>;
2) 数据成员WeakDependencies
WeakDependencies
是结构体CheckerInfo
的数据成员之一。其定义如下:
114 ConstCheckerInfoList WeakDependencies;
通过搜索源码发现,仅在template <bool IsWeak> void CheckerRegistry::resolveDependencies()
模板函数中会设置这两个数据成员的值。其源码实现如下:
298 template <bool IsWeak> void CheckerRegistry::resolveDependencies() {
299 for (const std::pair<StringRef, StringRef> &Entry :
300 (IsWeak ? Data.WeakDependencies : Data.Dependencies)) {
301
302 auto CheckerIt = binaryFind(Data.Checkers, Entry.first);
// 省略 ...
308 auto DependencyIt = binaryFind(Data.Checkers, Entry.second);
// 省略 ...
320 if (IsWeak)
321 CheckerIt->WeakDependencies.emplace_back(&*DependencyIt);
322 else
323 CheckerIt->Dependencies.emplace_back(&*DependencyIt);
324 }
325 }
通过 GDB 调试进入该函数后,打印IsWeak
、Entry
、CheckerIt
和DependencyIt
的值,如下所示。
第一次进入该函数时的打印结果之一:
(gdb) p IsWeak
$1 = true
(gdb) p Entry
$2 = {first = {static npos = 18446744073709551615, Data = 0x7fffe9ff0a88 "alpha.unix.StdCLibraryFunctionArgs", Length = 34}, second = {
static npos = 18446744073709551615, Data = 0x7fffe9fec520 "core.CallAndMessage", Length = 19}}
(gdb) p CheckerIt->FullName
$3 = {static npos = 18446744073709551615, Data = 0x7fffe9ff0a88 "alpha.unix.StdCLibraryFunctionArgs", Length = 34}
(gdb) p DependencyIt->FullName
$4 = {static npos = 18446744073709551615, Data = 0x7fffe9fec520 "core.CallAndMessage", Length = 19}
从上面的结果可以看出,由于模板参数IsWeak
的值为true
,所以会设置数据成员WeakDependencies
的值。
检查器之间的弱依赖关系通过Checkers.inc
文件中的CHECKER_WEAK_DEPENDENCY
宏定义指定。
Checkers.inc
文件中的对应内容如下:
318 #ifdef GET_CHECKER_WEAK_DEPENDENCIES
319 CHECKER_WEAK_DEPENDENCY("alpha.unix.StdCLibraryFunctionArgs", "core.CallAndMessage")
// 省略 ...
323 #endif // GET_CHECKER_WEAK_DEPENDENCIES
Checkers.inc:319 行的宏定义表示:检查器alpha.unix.StdCLibraryFunctionArgs
弱依赖于检查器core.CallAndMessage
。
因此,结构体CheckerInfo
中的数据成员WeakDependencies
的作用为:用于存放弱依赖的检查器有哪些。
第二次进入该函数时的打印结果之一:
(gdb) p IsWeak
$7 = false
(gdb) p Entry
$8 = {first = {static npos = 18446744073709551615, Data = 0x7fffe9fee8a8 "cplusplus.NewDelete", Length = 19}, second = {static npos = 18446744073709551615,
Data = 0x7fffe9fed1ec "unix.DynamicMemoryModeling", Length = 26}}
(gdb) p CheckerIt->FullName
$9 = {static npos = 18446744073709551615, Data = 0x7fffe9fee8a8 "cplusplus.NewDelete", Length = 19}
(gdb) p DependencyIt->FullName
$10 = {static npos = 18446744073709551615, Data = 0x7fffe9fed1ec "unix.DynamicMemoryModeling", Length = 26}
从上面的结果可以看出,由于模板参数IsWeak
的值为false
,所以会设置数据成员Dependencies
的值。
检查器之间的强依赖关系通过Checkers.inc
文件中的CHECKER_DEPENDENCY
宏定义指定。
Checkers.inc
文件中的对应内容如下:
253 #ifdef GET_CHECKER_DEPENDENCIES
// 省略 ...
280 CHECKER_DEPENDENCY("cplusplus.NewDelete", "unix.DynamicMemoryModeling")
// 省略 ...
316 #endif // GET_CHECKER_DEPENDENCIES
Checkers.inc:280 行的宏定义表示:检查器cplusplus.NewDelete
强依赖于检查器unix.DynamicMemoryModeling
。
对于检查器之间的强依赖关系,可以理解为:如果不启用被强依赖的检查器,那么所有强依赖于该检查器的检查器都不能正常使用。
因此,结构体CheckerInfo
中的数据成员Dependencies
的作用为:用于存放强依赖的检查器有哪些。
需要注意的是, 通过分析该模板函数的源码实现以及调试可以发现,数据成员Dependencies
和WeakDependencies
中仅包括直接依赖的检查器。
step 4: 再探数据成员IsHidden
在上面分析的template <bool IsWeak> void CheckerRegistry::resolveDependencies()
模板函数中有这样一段代码:
298 template <bool IsWeak> void CheckerRegistry::resolveDependencies() {
299 for (const std::pair<StringRef, StringRef> &Entry :
300 (IsWeak ? Data.WeakDependencies : Data.Dependencies)) {
// 省略 ...
313 // We do allow diagnostics from unit test/example dependency checkers.
314 assert((DependencyIt->FullName.startswith("test") ||
315 DependencyIt->FullName.startswith("example") || IsWeak ||
316 DependencyIt->IsHidden) &&
317 "Strong dependencies are modeling checkers, and as such "
318 "non-user facing! Mark them hidden in Checkers.td!");
// 省略 ...
325 }
从上面的断言条件可以看出,如果一个被强依赖的检查器,其检查器全称既不是以test
开头也不是以example
开头,并且其数据成员IsHidden
的值为false
,那么该断言将失败。
另外,断言失败的原因提示如下:
Strong dependencies are modeling checkers, and as such non-user facing! Mark them hidden in Checkers.td!
那么,其中的对用户不可见
意味着什么?
检查器unix.DynamicMemoryModeling
是被强依赖的检查器之一。在Checkers.inc
文件中,对应的CHECKER
宏定义如下:
CHECKER("unix.DynamicMemoryModeling", DynamicMemoryModeling, "The base of several malloc() related checkers. On it's own it emits no reports, but adds valuable information to the analysis when enabled.", "", true)
从上面的结果可以看出,检查器unix.DynamicMemoryModeling
的数据成员IsHidden
的值将是true
。
分别执行如下几个查看检查器的命令,结果如下:
$ clang -cc1 -analyzer-checker-help | grep DynamicMemoryModeling
$ clang -cc1 -analyzer-checker-help-alpha | grep DynamicMemoryModeling
$ clang -cc1 -analyzer-checker-help-developer | grep DynamicMemoryModeling
unix.DynamicMemoryModeling The base of several malloc() related checkers. On it's own it emits no reports, but adds valuable information to the analysis when enabled.
从上面的结果可以看出,对于数据成员IsHidden
的值为true
的检查器,无法通过clang -cc1 -analyzer-checker-help
和clang -cc1 -analyzer-checker-help-alpha
命令查看(对用户不可见)。但可以使用clang -cc1 -analyzer-checker-help-developer
命令查看(对开发者可见)。
因此,结构体CheckerInfo
中的数据成员IsHidden
的作用为:表示检查器是否对用户可见。
step 5: 数据成员CmdLineOptions
待补充
step 1: 成员函数isEnabled()
该函数的源码实现如下:
116 bool isEnabled(const CheckerManager &mgr) const {
117 return State == StateFromCmdLine::State_Enabled && ShouldRegister(mgr);
118 }
从上述分析可以得出,成员函数isEnabled()
的代码逻辑为:如果通过-analyzer-checker
标志显式地启用了检查器并且检查器注册函数shouldRegisterXXX
的返回值为true
,那么该函数的返回值为true
。
因此,结构体CheckerInfo
中的成员函数isEnabled()
的作用为:判断是否启用了检查器。
step 2: 成员函数isDisabled()
该函数的源码实现如下:
120 bool isDisabled(const CheckerManager &mgr) const {
121 return State == StateFromCmdLine::State_Disabled || !ShouldRegister(mgr);
122 }
从上述分析可以得出,成员函数isDisabled()
的代码逻辑为:如果通过-analyzer-disable-checker
标志显式地禁用了检查器或者检查器注册函数shouldRegisterXXX
的返回值为false
,那么该函数的返回值为true
。
因此,结构体CheckerInfo
中的成员函数isDisabled()
的作用为:判断是否禁用了检查器。
结构体clang::ento::CheckerInfo
的作用为: 用于封装创建一个检查器类实例所需要的所有数据。
其数据成员及用途如下:
数据成员 | 数据类型 | 用途 |
---|---|---|
Initialize | RegisterCheckerFn | 表示检查器注册函数registerXXX 的运行时内存地址 |
ShouldRegister | ShouldRegisterFunction | 表示检查器注册函数shouldRegisterXXX 的运行时内存地址 |
FullName | StringRef | 表示检查器的全称(包括检查器所属包的包名) |
Desc | StringRef | 表示检查器的描述 |
DocumentationUri | StringRef | 表示检查器文档的URI 地址 |
CmdLineOptions | CmdLineOptionList | ? |
IsHidden | bool | 表示检查器是否对用户可见。true ,表示对用户不可见(即无法通过clang -cc1 -analyzer-checker-help 和clang -cc1 -analyzer-checker-help-alpha 命令查看该检查器),但对开发者可见(即可以使用clang -cc1 -analyzer-checker-help-developer 命令查看该检查器)false ,表示对用户和开发者都可见 |
State | StateFromCmdLine | 表示检查器的启用状态。-analyzer-checker 标志指定的检查器,该字段的值为State_Enabled ,表示启用检查器。-analyzer-disable-checker 标志指定的检查器,该字段的值为State_Disabled ,表示禁用检查器。State_Unspecified (默认值),表示未指定。 |
Dependencies | ConstCheckerInfoList | 用于存放直接强依赖的检查器有哪些(不包括间接依赖的) |
WeakDependencies | ConstCheckerInfoList | 用于存放直接弱依赖的检查器有哪些(不包括间接依赖的) |
其成员函数及用途如下:
成员函数 | 函数原型 | 用途 |
---|---|---|
isEnabled() | bool isEnabled(const CheckerManager &mgr) const; | 判断是否启用了检查器。true ,表示通过-analyzer-checker 标志显式地启用了检查器并且检查器注册函数shouldRegisterXXX 的返回值为true 。 |
isDisabled() | bool isDisabled(const CheckerManager &mgr) const; | 判断是否禁用了检查器。true ,表示通过-analyzer-disable-checker 标志显式地禁用了检查器或者检查器注册函数shouldRegisterXXX 的返回值为false 。 |
下一篇:LLVM 之 Clang 源码分析篇(2):clang::ento::CheckerRegistryData 结构体
上一篇:上一级目录