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的源码实现目录如下:


数据成员

step 1: 数据成员InitializeShouldRegisterFullNameIsHidden

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

从上面的结果可以看出:

而这些参数的值正是文件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_UnspecifiedState_DisabledState_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::stringbool> &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: 数据成员DependenciesWeakDependencies

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 调试进入该函数后,打印IsWeakEntryCheckerItDependencyIt的值,如下所示。

第一次进入该函数时的打印结果之一:

(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的作用为:用于存放强依赖的检查器有哪些。

需要注意的是, 通过分析该模板函数的源码实现以及调试可以发现,数据成员DependenciesWeakDependencies中仅包括直接依赖的检查器。

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-helpclang -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-helpclang -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

  • References


    下一篇:LLVM 之 Clang 源码分析篇(2):clang::ento::CheckerRegistryData 结构体

    上一篇:上一级目录

    首页