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 CheckPreCall(const CallEvent &Call,CheckerContext &C) const;

};
}
void MainCallChecker::CheckPreCall(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"));
}
    ExplodedNode *N = C.generateSink();
    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 checkPreCall(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::CheckPreCall(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"));
}
    ExplodedNode *N = C.generateSink();
    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 &registry)
{
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