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

Author: stormQ

Created: Monday, 19. April 2021 07:05PM

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



摘要

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


研究过程


类模板clang::ento::Checker<CHECK1, CHECKs>的源码实现目录如下:


数据成员

step 1: 数据成员Name

数据成员Name的定义如下:

493   CheckerNameRef Name;

数据类型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 };

通过搜索源码发现,函数clang::ento::CheckerManager::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)...);
211     checker->Name = CurrentCheckerName;
// 省略 ...
216   }

上述代码中的CurrentCheckerName是类clang::ento::CheckerManager的数据成员之一,其作用为:表示检查器的全称(包括检查器所属包的包名)。

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

因此,类模板clang::ento::Checker<CHECK1, CHECKs>中的数据成员Name的作用为:表示检查器的全称(包括检查器所属包的包名)。

返回上一级


成员函数

step 1: 成员函数getCheckerName()

该函数是由基类clang::ento::CheckerBase定义并实现的,其源码实现如下(定义在 clang/lib/StaticAnalyzer/Core/Checker.cpp 文件中):

25 CheckerNameRef CheckerBase::getCheckerName() const { return Name; }

从上面的代码可以看出,类模板clang::ento::Checker<CHECK1, CHECKs>中的成员函数getCheckerName()的作用为:返回数据类型为CheckerNameRef的检查器全称。

step 2: 成员函数getTagDescription()

该函数是由基类clang::ProgramPointTag定义的纯虚函数。其函数声明如下(定义在 clang/include/clang/Analysis/ProgramPoint.h 文件中):

39 class ProgramPointTag {
40 public:
// 省略 ...
43   virtual StringRef getTagDescription() const 0;
// 省略 ...
50 };

并且,该函数由基类clang::ento::CheckerBase实现。其源码实现如下(定义在 clang/lib/StaticAnalyzer/Core/Checker.cpp 文件中):

21 StringRef CheckerBase::getTagDescription() const {
22   return getCheckerName().getName();
23 }

从上面的代码可以看出,类模板clang::ento::Checker<CHECK1, CHECKs>中的成员函数getTagDescription()的作用为:返回数据类型为StringRef的检查器全称。

step 3: 成员函数_register()

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

516 template <typename CHECK1, typename... CHECKs>
517 class Checker : public CHECK1, public CHECKs..., public CheckerBase {
518 public:
519   template <typename CHECKER>
520   static void _register(CHECKER *checker, CheckerManager &mgr) {
521     CHECK1::_register(checker, mgr);
522     Checker<CHECKs...>::_register(checker, mgr);
523   }
524 };

由于在实际使用时,类模板参数CHECK1和可变模板参数CHECKs都是程序点对应的 C++ 类,比如:clang::ento::check::PreCall。而函数模板参数CHECKER表示具象检查器类。

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

也就是说,如果一个具象检查器类订阅了多个程序点,那么调用其静态成员函数_register()后会导致这些程序点类的静态成员函数_register()按照被订阅的先后顺序依次被调用。

比如,具象检查器类CStringChecker(类定义位于匿名空间中)订阅了多个程序点,按照订阅的先后顺序依次为clang::ento::eval::Callclang::ento::check::PreStmt<clang::DeclStmt>clang::ento::check::LiveSymbolsclang::ento::check::DeadSymbolsclang::ento::check::RegionChanges。如果调用该具象检查器类的静态成员函数_register(),那么相当于调用Checker1::_register()函数。

函数Checker1::_register()的实现如下:

using Checker1 = clang::ento::Checker<clang::ento::eval::Call, clang::ento::check::PreStmt<clang::DeclStmt>, clang::ento::check::LiveSymbols, clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>;

static void Checker1::_register<(anonymous namespace)::CStringChecker> (CHECKER *checker, CheckerManager &mgr) {
  clang::ento::eval::Call::_register(checker, mgr);

  using Checker2 = clang::ento::Checker<clang::ento::check::PreStmt<clang::DeclStmt>, clang::ento::check::LiveSymbols, clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>;
  Checker2::_register(checker, mgr);
}

注:函数Checker1::_register()中会调用Checker2::_register()

函数Checker2::_register()的实现如下:

using Checker2 = clang::ento::Checker<clang::ento::check::PreStmt<clang::DeclStmt>, clang::ento::check::LiveSymbols, clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>;

static void Checker2::_register<(anonymous namespace)::CStringChecker> (CHECKER *checker, CheckerManager &mgr) {
  clang::ento::check::PreStmt<clang::DeclStmt>::_register(checker, mgr);

  using Checker3 = clang::ento::Checker<clang::ento::check::LiveSymbols, clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>;
  Checker3::_register(checker, mgr);
}

注:函数Checker2::_register()中会调用Checker3::_register()

函数Checker3::_register()的实现如下:

using Checker3 = clang::ento::Checker<clang::ento::check::LiveSymbols, clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>;

static void Checker3::_register<(anonymous namespace)::CStringChecker> (CHECKER *checker, CheckerManager &mgr) {
  clang::ento::check::LiveSymbols::_register(checker, mgr);

  using Checker4 = clang::ento::Checker<clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>;
  Checker4::_register(checker, mgr);
}

注:函数Checker3::_register()中会调用Checker4::_register()

函数Checker4::_register()的实现如下:

using Checker4 = clang::ento::Checker<clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>;

static void Checker4::_register<(anonymous namespace)::CStringChecker> (CHECKER *checker, CheckerManager &mgr) {
  clang::ento::check::DeadSymbols::_register(checker, mgr);

  using Checker5 = clang::ento::Checker<clang::ento::check::RegionChanges>;
  Checker5::_register(checker, mgr);
}

注:函数Checker4::_register()中会调用Checker5::_register()

函数Checker5::_register()的实现如下:

using Checker5 = clang::ento::Checker<clang::ento::check::RegionChanges>;

static void Checker5::_register<(anonymous namespace)::CStringChecker> (CHECKER *checker, CheckerManager &mgr) {
  clang::ento::check::RegionChanges::_register(checker, mgr);
}

需要注意的是, 函数Checker5::_register()的实现来自于类模板clang::ento::Checker<CHECK1>。其源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/Checker.h 文件中):

526 template <typename CHECK1>
527 class Checker<CHECK1> : public CHECK1, public CheckerBase {
528 public:
529   template <typename CHECKER>
530   static void _register(CHECKER *checker, CheckerManager &mgr) {
531     CHECK1::_register(checker, mgr);
532   }
533 };

另外,如果具象检查器类仅订阅了一个程序点,那么调用该具象检查器类的静态成员函数_register()会直接调用类模板clang::ento::Checker<CHECK1>中的静态成员函数_register()

通过 GDB 调试具象检查器类CStringChecker时,检查器注册函数的调用堆栈如下:

(gdb) bt
#0  clang::ento::Checker<clang::ento::check::RegionChanges>::_register<(anonymous namespace)::CStringChecker> (checker=0x555555671e80, mgr=...)
    at /home/xxq/git-projects/llvm-project/clang/include/clang/StaticAnalyzer/Core/Checker.h:531
#1  0x00007fffe322e24c in clang::ento::Checker<clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>::_register<(anonymous namespace)::CStringChecker>
    (checker=0x555555671e80, mgr=...) at /home/xxq/git-projects/llvm-project/clang/include/clang/StaticAnalyzer/Core/Checker.h:522
#2  0x00007fffe322dfd8 in clang::ento::Checker<clang::ento::check::LiveSymbols, clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>::_register<(anonymous namespace)::CStringChecker> (checker=0x555555671e80, mgr=...) at /home/xxq/git-projects/llvm-project/clang/include/clang/StaticAnalyzer/Core/Checker.h:522
#3  0x00007fffe322dd1f in clang::ento::Checker<clang::ento::check::PreStmt<clang::DeclStmt>, clang::ento::check::LiveSymbols, clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>::_register<(anonymous namespace)::CStringChecker> (checker=0x555555671e80, mgr=...)
    at /home/xxq/git-projects/llvm-project/clang/include/clang/StaticAnalyzer/Core/Checker.h:522
#4  0x00007fffe322da9a in clang::ento::Checker<clang::ento::eval::Call, clang::ento::check::PreStmt<clang::DeclStmt>, clang::ento::check::LiveSymbols, clang::ento::check::DeadSymbols, clang::ento::check::RegionChanges>::_register<(anonymous namespace)::CStringChecker> (checker=0x555555671e80, mgr=...)
    at /home/xxq/git-projects/llvm-project/clang/include/clang/StaticAnalyzer/Core/Checker.h:522
#5  0x00007fffe322d2ec in clang::ento::CheckerManager::registerChecker<(anonymous namespace)::CStringChecker> (this=0x555555660c20)
    at /home/xxq/git-projects/llvm-project/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h:213
#6  0x00007fffe322b164 in clang::ento::registerCStringModeling (Mgr=...)
    at /home/xxq/git-projects/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp:2443
#7  0x00007fffe9f92ede in clang::ento::CheckerRegistry::initializeManager (this=0x7fffffffbb90, CheckerMgr=...)
    at /home/xxq/git-projects/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp:466
省略 ...

接下来,以程序点clang::ento::check::PreCall为例,分析其静态成员函数_register()背后的实现。

clang::ento::check::PreCall的静态成员函数_register()的源码实现如下(定义在 clang/include/clang/StaticAnalyzer/Core/Checker.h 文件中):

164 class PreCall {
165   template <typename CHECKER>
166   static void _checkCall(void *checker, const CallEvent &msg,
167                          CheckerContext &C) {
168     ((const CHECKER *)checker)->checkPreCall(msg, C);
169   }
170 
171 public:
172   template <typename CHECKER>
173   static void _register(CHECKER *checker, CheckerManager &mgr) {
174     mgr._registerForPreCall(
175      CheckerManager::CheckCallFunc(checker, _checkCall<CHECKER>));
176   }
177 };

从上面的代码可以看出,函数_register()中仅调用了clang::ento::CheckerManager::_registerForPreCall()函数。

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

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

因此,类clang::ento::check::PreCall的静态成员函数_register()的作用为:将该程序点的派生类实例和其回调函数存储到检查器管理类clang::ento::CheckerManager中。

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

返回上一级


研究结论

类模板clang::ento::Checker<CHECK1, CHECKs>的作用为: 作为具象检查器类的基类。

其数据成员及用途如下:

数据成员 数据类型 用途
Name CheckerNameRef 表示检查器的全称(包括检查器所属包的包名)

其成员函数及用途如下:

成员函数 函数原型 用途
getCheckerName() CheckerNameRef CheckerBase::getCheckerName() const 返回数据类型为CheckerNameRef的检查器全称
getTagDescription() virtual StringRef getTagDescription() const 返回数据类型为StringRef的检查器全称
_register() template <typename CHECKER> static void _register(CHECKER *checker, CheckerManager &mgr) 将检查器实例和其实现的各回调函数成对地注册到检查器管理类中

References


下一篇:LLVM 之 Clang 源码分析篇(6):clang::ento::CheckerContext 类

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

首页