LLVM 之 Clang 源码分析篇(4):clang::ento::CheckerManager 类

Author: stormQ

Created: Sunday, 18. April 2021 02:18PM

Last Modified: Thursday, 22. April 2021 08:11AM



摘要

本文基于release/12.x版本的 LLVM 源码,研究了clang::ento::CheckerManager类中各数据成员表示的意义以及成员函数的作用。从而,有助于理解 Clang 静态分析器源码实现的其他相关部分。


研究过程


clang::ento::CheckerManager的源码实现目录如下:


数据成员

step 1: 数据成员CheckerTags

数据成员CheckerTags的定义如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

627   llvm::DenseMap<CheckerTag, CheckerRef> CheckerTags;

数据类型CheckerTag的定义如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

192   using CheckerTag = const void *;

数据类型CheckerRef的定义如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

191   using CheckerRef = CheckerBase *;

因此,数据成员CheckerTags的实际定义如下:

llvm::DenseMap<const void *, CheckerBase *> CheckerTags;

通过搜索源码发现,成员函数registerChecker()中会设置该数据成员的值。其源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

204   template <typename CHECKER, typename... AT>
205   CHECKER *registerChecker(AT &&... Args) {
206     CheckerTag tag = getTag<CHECKER>();
207     CheckerRef &ref = CheckerTags[tag];
208     assert(!ref && "Checker already registered, use getChecker!");
209 
210     CHECKER *checker = new CHECKER(std::forward<AT>(Args)...);
// 省略 ...
214     ref = checker;
215     return checker;
216   }

从上面的代码可以看出,数据成员CheckerTags中元素的key值即成员函数getTag()的函数返回值,value值是一个指向检查器类实例的指针。

由于成员函数getTag()的作用为:为每个检查器类生成唯一的标识符。

因此,类CheckerManager中的数据成员CheckerTags的作用为:用于存储已创建的检查器类实例。其元素的key值表示检查器类实例的唯一标识符,value值表示检查器类实例的内存地址。

step 2: 数据成员CurrentCheckerName

数据成员CurrentCheckerName的定义如下:

132   CheckerNameRef CurrentCheckerName;

数据类型CheckerNameRef的定义如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

107 class CheckerNameRef {
108   friend class ::clang::ento::CheckerRegistry;
109 
110   StringRef Name;
111 
112   explicit CheckerNameRef(StringRef Name) : Name(Name{}
113 
114 public:
115   CheckerNameRef() = default;
116 
117   StringRef getName(const return Name; }
118   operator StringRef(const return Name; }
119 };

通过搜索源码发现,成员函数setCurrentCheckerName()中会设置该数据成员的值。其源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

163   void setCurrentCheckerName(CheckerNameRef name) { CurrentCheckerName = name; }

通过搜索源码发现,仅在CheckerRegistry::initializeManager()函数中会调用该成员函数。其源码实现如下(定义在 clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp 文件中):

462 void CheckerRegistry::initializeManager(CheckerManager &CheckerMgr) const {
463   // Initialize the CheckerManager with all enabled checkers.
464   for (const auto *Checker : Data.EnabledCheckers) {
465     CheckerMgr.setCurrentCheckerName(CheckerNameRef(Checker->FullName));
466     Checker->Initialize(CheckerMgr);
467   }
468 }

结构体clang::ento::CheckerInfo中的数据成员FullName的作用为:表示检查器的全称(包括检查器所属包的包名),

注:这里直接给出了结论,具体研究过程可参考笔者的另一篇文章:《LLVM 之 Clang 源码分析篇(1):clang::ento::CheckerInfo 结构体》。

因此,类CheckerManager中的数据成员CurrentCheckerName的作用为:表示检查器的全称。

step 3: 数据成员CheckerDtors

数据成员CheckerDtors的定义如下:

629   std::vector<CheckerDtor> CheckerDtors;

从上面的代码可以看出,数据成员CheckerDtors是一个std::vector向量,其元素的数据类型为CheckerDtor

数据类型CheckerDtor的定义如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

193   using CheckerDtor = CheckerFn<void ()>;

因此,数据成员CheckerDtors的实际定义如下:

std::vector<clang::ento::CheckerFn<void()>> CheckerDtors;

通过搜索源码发现,成员函数registerChecker()中会设置该数据成员的值。其源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

204   template <typename CHECKER, typename... AT>
205   CHECKER *registerChecker(AT &&... Args) {
// 省略 ...
210     CHECKER *checker = new CHECKER(std::forward<AT>(Args)...);
// 省略 ...
212     CheckerDtors.push_back(CheckerDtor(checker, destruct<CHECKER>));
// 省略 ...
216   }

从上面的代码可以看出,调用语句CheckerDtor(checker, destruct<CHECKER>)实质上就是调用CheckerFn<void ()>(checker, destruct<CHECKER>)。因此,模板类CheckerFn的构造函数会被调用。

模板类CheckerFn的定义如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

61 template <typename RET, typename... Ps>
62 class CheckerFn<RET(Ps...)> {
63   using Func = RET (*)(void *, Ps...);
64 
65   Func Fn;
66 
67 public:
68   CheckerBase *Checker;
69 
70   CheckerFn(CheckerBase *checker, Func fn) : Fn(fn), Checker(checker) {}
71 
72   RET operator()(Ps... ps) const {
73     return Fn(Checker, ps...);
74   }
75 };

从上面的代码可以看出,模板类CheckerFn的构造函数有两个参数,参数checker是一个指向检查器类实例的指针,参数fn是一个函数指针。这里,参数fn的值就是成员函数destruct()的内存地址。

由于成员函数destruct()的作用为:销毁指定的检查器类实例。

因此,类CheckerManager中的数据成员CheckerDtors的作用为:用于存储所有的检查器类实例及其销毁函数。

step 4: 数据成员PreCallCheckers

数据成员PreCallCheckers的定义如下:

666   std::vector<CheckCallFunc> PreCallCheckers;

从上面的代码可以看出,数据成员PreCallCheckers是一个std::vector向量,其元素的数据类型为CheckCallFunc

数据类型CheckCallFunc的定义如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

489   using CheckCallFunc =
490       CheckerFn<void (const CallEvent &, CheckerContext &)>;

因此,数据成员PreCallCheckers的实际定义如下:

std::vector<clang::ento::CheckerFn<void(const clang::ento::CallEvent&, clang::ento::CheckerContext&)>> PreCallCheckers;

通过搜索源码发现,成员函数_registerForPreCall()中会设置该数据成员的值。其源码实现如下(定义在 clang/lib/StaticAnalyzer/Core/CheckerManager.cpp 文件中):

817 void CheckerManager::_registerForPreCall(CheckCallFunc checkfn) {
818   PreCallCheckers.push_back(checkfn);
819 }

通过搜索源码发现,仅在函数clang::ento::check::PreCall::_register()中会调用该成员函数。其源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/Checker.h 文件中):

172   template <typename CHECKER>
173   static void _register(CHECKER *checker, CheckerManager &mgr) {
174     mgr._registerForPreCall(
175      CheckerManager::CheckCallFunc(checker, _checkCall<CHECKER>));
176   }

从上面的代码可以看出,其代码逻辑为:

因此,类CheckerManager中的数据成员PreCallCheckers的作用为:用于存储所有订阅程序点clang::ento::check::PreCall的检查器类实例及各自的回调函数。

需要注意的是, 这里存储的回调函数是clang::ento::check::PreCall::_checkCall<CHECKER>(),而不是具象检查器类实现的回调函数checkPreCall()

返回上一级


成员函数

step 1: 成员函数getTag()

该函数的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

624   template <typename T>
625   static void *getTag() static int tag; return &tag; }

从上面的代码可以看出,其代码逻辑为:模板参数值不同时,返回不同的静态局部变量的内存地址。

通过搜索源码发现,成员函数registerChecker()函数中会调用该成员函数。其源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

204   template <typename CHECKER, typename... AT>
205   CHECKER *registerChecker(AT &&... Args) {
206     CheckerTag tag = getTag<CHECKER>();
// 省略 ...
216   }

从上面的代码可以看出,在实际使用时成员函数registerChecker()的模板参数为检查器类。

因此,类CheckerManager中的成员函数getTag()的作用为:为每个检查器类生成唯一的标识符。

step 2: 成员函数destruct()

该函数的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

621   template <typename CHECKER>
622   static void destruct(void *obj) delete static_cast<CHECKER *>(obj); }

从上面的代码可以看出,其代码逻辑为:销毁一个由模板参数指定的类的实例。

通过搜索源码发现,成员函数registerChecker()函数中会调用该成员函数。其源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

204   template <typename CHECKER, typename... AT>
205   CHECKER *registerChecker(AT &&... Args) {
// 省略 ...
210     CHECKER *checker = new CHECKER(std::forward<AT>(Args)...);
// 省略 ...
212     CheckerDtors.push_back(CheckerDtor(checker, destruct<CHECKER>));
// 省略 ...
216   }

从上面的代码可以看出,在实际使用时成员函数destruct()的模板参数为检查器类。

因此,类CheckerManager中的成员函数destruct()的作用为:销毁指定的检查器类实例。

step 3: 成员函数registerChecker()

该函数的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/CheckerManager.h 文件中):

199   /// Used to register checkers.
200   /// All arguments are automatically passed through to the checker
201   /// constructor.
202   ///
203   /// \returns a pointer to the checker object.
204   template <typename CHECKER, typename... AT>
205   CHECKER *registerChecker(AT &&... Args) {
206     CheckerTag tag = getTag<CHECKER>();
207     CheckerRef &ref = CheckerTags[tag];
208     assert(!ref && "Checker already registered, use getChecker!");
209 
210     CHECKER *checker = new CHECKER(std::forward<AT>(Args)...);
211     checker->Name = CurrentCheckerName;
212     CheckerDtors.push_back(CheckerDtor(checker, destruct<CHECKER>));
213     CHECKER::_register(checker, *this);
214     ref = checker;
215     return checker;
216   }

类模板clang::ento::Checker<CHECK1, CHECKs>中的成员函数_register()的作用为:将检查器实例和其实现的各回调函数成对地注册到检查器管理类中。

注:这里直接给出了结论,具体研究过程可参考笔者的另一篇文章:《LLVM 之 Clang 源码分析篇(5):clang::ento::Checker< CHECK1, CHECKs > 类模板》。

因此,成员函数registerChecker()的代码逻辑为:

因此,类clang::ento::CheckerManager中的成员函数registerChecker()的作用为:创建指定的检查器类实例,并注册其订阅的程序点。

step 4: 成员函数_registerForPreCall()

该函数的源码实现如下(定义在 clang/lib/StaticAnalyzer/Core/CheckerManager.cpp 文件中):

817 void CheckerManager::_registerForPreCall(CheckCallFunc checkfn) {
818   PreCallCheckers.push_back(checkfn);
819 }

由于类CheckerManager中的数据成员PreCallCheckers的作用为:用于存储所有订阅程序点clang::ento::check::PreCall的检查器类实例及各自的回调函数。

因此,类clang::ento::CheckerManager中的成员函数_registerForPreCall()的作用为:添加订阅程序点clang::ento::check::PreCall的检查器类实例及其回调函数。

返回上一级


研究结论

clang::ento::CheckerManager的作用为: 作为检查器管理类。

其数据成员及用途如下:

数据成员 数据类型 用途
CheckerTags llvm::DenseMap<CheckerTag, CheckerRef> 用于存储已创建的检查器类实例。其元素的key值表示检查器类实例的唯一标识符,value值表示检查器类实例的内存地址。
CurrentCheckerName CheckerNameRef 表示检查器的全称
CheckerDtors std::vector<CheckerDtor> 用于存储所有的检查器类实例及其销毁函数
PreCallCheckers std::vector<CheckCallFunc> 用于存储所有订阅程序点clang::ento::check::PreCall的检查器类实例及各自的回调函数

其成员函数及用途如下:

成员函数 函数原型 用途
getTag() template <typename T> static void *getTag() 为每个检查器类生成唯一的标识符
destruct() template <typename CHECKER> static void destruct(void *obj) 销毁指定的检查器类实例
registerChecker() template <typename CHECKER, typename... AT> CHECKER *registerChecker(AT &&... Args) 创建指定的检查器类实例,并注册其订阅的程序点
_registerForPreCall() 函数原型 添加订阅程序点clang::ento::check::PreCall的检查器类实例及其回调函数

References


下一篇:LLVM 之 Clang 源码分析篇(5):clang::ento::Checker< CHECK1, CHECKs > 类模板

上一篇:LLVM 之 Clang 源码分析篇(3):clang::ento::CheckerRegistry 类

首页