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>
的源码实现目录如下:
头文件为clang/include/clang/StaticAnalyzer/Core/Checker.h
源文件为clang/lib/StaticAnalyzer/Core/Checker.cpp
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()
的代码逻辑为:
首先,调用程序点CHECK1
类的静态成员函数_register()
。
然后,调用参数包CHECKs
中所有程序点类的静态成员函数_register()
。
也就是说,如果一个具象检查器类订阅了多个程序点,那么调用其静态成员函数_register()
后会导致这些程序点类的静态成员函数_register()
按照被订阅的先后顺序依次被调用。
比如,具象检查器类CStringChecker
(类定义位于匿名空间中)订阅了多个程序点,按照订阅的先后顺序依次为clang::ento::eval::Call
、clang::ento::check::PreStmt<clang::DeclStmt>
、clang::ento::check::LiveSymbols
、clang::ento::check::DeadSymbols
和clang::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) |
将检查器实例和其实现的各回调函数成对地注册到检查器管理类中 |
下一篇:LLVM 之 Clang 源码分析篇(6):clang::ento::CheckerContext 类