Clang Static Analyzer-使用手册-编写Checker代码实现
作者:互联网
Clang Static Analyzer-使用手册-编写Checker代码实现
示例程序MainCallChecker.cpp
#include"ClangSACheckers.h"
#include"clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include"clang/StaticAnalyzer/Core/Checker.h"
#include"clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include"clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
using namespace clang;
using namespace clang::ento;
namespace
{
class MainCallChecker : public Checker<check::PreCall>
{
mutable std::unique_ptr<BugType> BT;
public:
void CheckerPreCall(const CallEvent &Call,CheckerContext &C) const;
};
}
void MainCallChecker::CheckerPreCall(const CallEvent& Call, CheckerContext& C) const
{
if (const IdentifierInfo *II = Call.getCalleeIdentifier())
{
if (II->isStr("main"))
{
if (!BT)
{
BT.reset(new BugType(this, "Call to main", "Example checker"));
}
ExplodeNode *N = C.generateErrorNode();
auto Report = llvm::make_unique<BugReport>(*BT, BT->getName(), N);
C.emitReport(std::move(Report));
}
}
}
void ento::registerMainCallChecker(CheckerManager& Mgr)
{
Mgr.registerChecker<MainCallChecker>();
}
1 申明一个Checker类
namespace
{
class MainCallChecker : public Checker<check::PreCall>
{
mutable std::unique_ptr<BugType> BT;
public:
void CheckerPreCall(const CallEvent &Call,CheckerContext &C) const;
};
}
在CSA中的Checker类是通过继承Checker<...>来实现的,其中Checker<...>中的模板参数表示Checker中的回调函数列表,比如这里有一个check::PreCall,参数,那么就有个CheckerPreCall回调函数
Checker类的定义通常是放在一个匿名函数的空间,去避免命名冲突
MainCallChecker关联了check::PreCall Event(事件),而CheckPreCall()回调函数定义在checker类里面,那么只要每次path-sensitive引擎分析函数调用时,都会调用这个CheckPreCall来分析它
2 回调函数的写法
void MainCallChecker::CheckerPreCall(const CallEvent& Call, CheckerContext& C) const
{
if (const IdentifierInfo *II = Call.getCalleeIdentifier())
{
if (II->isStr("main"))
{
if (!BT)
{
BT.reset(new BugType(this, "Call to main", "Example checker"));
}
ExplodeNode *N = C.generateErrorNode();
auto Report = llvm::make_unique<BugReport>(*BT, BT->getName(), N);
C.emitReport(std::move(Report));
}
}
}
回调函数中的CallEvent结构体在回调函数中有效地包含了所有analyzer核心为我们收集的函数调用事件的数据,以及这个结构体包含了有关被调函数的所有信息和参数的值
因为这个MainCallChecker是path-sensitive类型的Checker,报错信息比用C++语法树检查会多得多,特别是它能知道你通过函数指针来调用函数,因为它是可以预测函数指针执行的路径。
const IdentifierInfo *II = Call.getCalleeIdentifier()
这里用IdentifierInfo结构体来保存CallEvent结构体的被调函数的返回值
如果getCalleeIdentifier返回一个NULL函数指针就会返回我们自己写的这个函数然后继续分析
if (II->isStr("main"))
在这段代码里,只对函数名字是否叫main 感兴趣,一切都是建立在这个基础下的分析
3 写出bug reports
CSA会提供一个bug reports来让使用者更加方便查看bug的逻辑
我们用另外一个可用的对象在我们的回调函数里,CheckerContext这个结构体,这个结构体超级nb它包含了各种各样的函数checker能够在分析和影响分析流程的时候获取大量信息
在我们的Checker中包含了一个BT变量在里面,它存放了bug的类型并且明显表示了这个bug属于那种checker,一个Checker可能有多种bug类型,它们通常保存和重复使用在这个checker中为了来提高性能
BT.reset(new BugType(this, "Call to main", "Example checker"));
这里初始化bug种类,名称叫做"call to main",种类叫做Example Checker,初始化它已经被初始化了,也就是前面的BT!=NULL
ExplodeNode *N = C.generateErrorNode();
这里,利用CheckerContext结构体下的一个函数创造了一个sink节点,这里意味着如果遇到这个缺陷程序很可能直接崩溃,那么接下来的分析也就没有意义了,这个节点表示Checker中的一个节点而已,如果发现了缺陷但是缺陷不重要没有必要停止分析
auto Report = llvm::make_unique<BugReport>(*BT, BT->getName(), N);
最后在这里,这个Checker创建了一个新的BugReport对象,这个对象针对我们之前生成的sink节点来抛出报文report
C.emitReport(std::move(Report));
最后通过emitReport方法传递report给CheckerContext,Report会聚集、删除重复数据,然后再很好的呈现再user面前
4 注册Checker
void ento::registerMainCallChecker(CheckerManager& Mgr)
{
Mgr.registerChecker<MainCallChecker>();
}
在最后有一段神奇的小代码,在分析启动的时候真正创建了一个Checker实例
可以用这段代码去禁止某些Checkers在编译程序时调用,设置Checkers之间的依赖,设置Checker选项
这段代码创建了一个MainCallChecker的实例,来提供尚未展开的事件
实现Checker总结
1 申明一个Checker类
2 写Checker的回调函数
3 写cheker的bugreport
4 注册checker
将checker编译为一个独立模板
可以把Checker编译为一个共享插件库来处理,这样就可以不用改Checkers.td和CMakeLists.txt也可以运行
你可以编写Checker作为一个独立库,在运行的合适的时候再加载它
在编译为插件的情况下,用于注册检查器的语法会发生变化。可以没必要包含ClangSAChecker.h头文件,但是你要包含CheckerRegistry.h头文件
#include"clang/StaticAnalyzer/Core/CheckerRegistry.h"
然后定义一个externally函数再我们的库里面来将再分析器的CheckerRegistry中动态注册Checker
extern "c" void clang_registerCheckers(CheckerRegistry ®istry)
{
registry.addChecker<MainCallChecker>("aphar.core.MainCallChecker",
"Checks for calls to main");
}//第一个表示Checker类型,第二个表示Checker的描述
还需要注意的是Clang API的版本需要和plugin插件版本相同,所以Checker需要保存版本的字符串clang_analyzerAPIVersionString来作为一个外部调用来实现版本兼容
extern "C" const char clang_analyzerAPIVersionString[] =
CLANG_ANALYZER_API_VERSION_STRING;
通过-clang -cc1 -load xxxx.xxx这个常用插件语法来加载Checker,加载后就会显示再Checker的列表里面了,
可以通过 clang -cc1 -analyzer-checker-help来查看Checker List
可以通过 -analyzer-checker来指定使用添加的checker
标签:const,checker,clang,BT,Checker,使用手册,Analyzer,Call 来源: https://www.cnblogs.com/Sna1lGo/p/14637886.html