首页 > 其他 > 详细

Clang Static Analyzer-使用手册-基于AST的Checkers

时间:2021-04-12 22:35:31      阅读:64      评论:0      收藏:0      [点我收藏+]

Clang Static Analyzer-使用手册-基于AST的Checkers

很多简单的Check可以通过查看程序语法树和捕获不需要的代码模式来实现,不使用CSA的path-sensitive engine的Checker很快,而且通常会有一个很高的正报几率,但是只能捕获非常有限的一些问题

比如AST visitors和AST matchers:

基于AST的check不是Clang Static Analyzer的强项,如果只用基于AST的信息,那么可以用基于clang的任何工具来配合

在官方的CSA里有一些基础AST的check,但是通常基于AST的checker会转到用clang-tidy 工具

(路径不敏感的Checker的回调函数)Path-insensitive checker callbacks

CSA的Path-sensitive引擎只有在至少一个Checker调用了需要进行Path-sensitive分析的回调函数后才开始执行

如果单纯使用基于AST的checker而不用path-sensitive,会快很多。因为基于AST的checker不会构造需要分析的一些数据结构,只是定义了几个AST的回调函数,最有用且不会触发Path-sensitive引擎的checker是 check::EndOfTranslationUnit和check::ASTCodeBody

 

check::EndOfTranslationUnit

void checkEndOfTranslationUnit(const TranslationUnitDecl *TU,AnalysisManager &AM,BugReporter &BR) const;

在这个回调函数里,程序的完整AST可以用来被分析

访问AST的入口点第一个参数TU来提供

当不仅需要检查可执行代码,还需要检查声明的时候经常使用这个回调函数

 

check::ASTCodeBody

void checkASTCodeBody(const Decl *D,AnalysisManager &AM,BugReporter &BR)const;

在这个回调函数中,每次调用都会提供函数的申明和分析器分析函数的代码段

需要分析的函数体可以作为D->getBody()使用

当只需要分析可执行代码的时候,这个回调函数就非常方便

check::ASTDecl<T>

void checkASTDecl(const T*D,AnalysisManager &Mgr,BugReporter &BR)const;

对于类型为T的所有AST声明(例如,如果T是VarDecl或者则调用所有变量,如果T的FieldDecl类,则调用所有类字段)

对于申明visitros来说,这个操作比较方便简单

AST visitors

AST visitors的机制是探索clang的AST最好用的工具,Clang提供了大量用简单语法的visitors给AST

对于实现checkers,两种visitors是最有用的

ConstmtVisitor检查代码使用范围最广的
ConstDeclVisitor 有时用来检查代码体外部的申明(如全局变量)

如果要使用visitor,需要从中继承一个类并为不同类型的AST节点实现visitor回调。如果没有为特定节点实现回调,就会调用更多通用节点的回调函数

例如: 将会在以下回调函数之一访问:CXXOperatorCallExpr

VisitCXXOperatorCallExpr(...),

VisitCallExpr(...),

VisitExpr(...),

VisitStmt(...),

调用最先确定的回调函数

实现一个简答的visitor(Implementing a simple statement visitor)

例如:我们可以尝试下能否重写alpha.core.MainCallChecker把它变为一个AST visitor

首先申明一个AST visitor,这个visitor存储了对BugReporter的引用来为了方便抛出Path-insensitive的报告和当前的AnalysisDeclContext,它是生成错误报告的判断位置所必需的。AnalysisDeclContext还封装了正在分析的原始函数

namespace {
class WalkAST : public ConstStmtVisitor < WalkAST > {
BugReporter & BR ;
AnalysisDeclContext * ADC ;
void VisitChildren (const Stmt *S);
public :
WalkAST (BugReporter &Reporter, AnalysisDeclContext *Context )
: BR(Reporter),ADC(Context) {}
void VisitStmt (const Stmt * S);
void VisitCallExpr (const CallExpr *CE);
};
}

这个visitor定义了两个public回调函数:VisitCallExpr(...)函数用于特殊处理函数调用表达式,VisitStmt(...)用于访问所有其他类型的语句

VisitChildren(const Stmt *s)

这些回调函数有一个共同点:每次访问完他们的语句后他们需要去访问子语句,这个操作通常由一个子函数VisitChildren(...)来处理

void WalkAST :: VisitChildren(const Stmt *S){
for (Stmt::const_child_iterator I=S->child_begin(),E=S->child_end();I!=E; ++ I )
{
if ( const Stmt * Child = *I)
Visit (Child);
}
}

VisitStmt(const Stmt *S)

这里还暂时用不到它,所以直接在内部调用Children函数就好了

void WalkAST::VisitStmt(const Stmt *S)
{
VisitChildren(S);
}

VisitCallExpr(const CallExpr *CE)

大多数的check逻辑存储在VisitCallExpr(...)中,获取得到当前调用表达式的函数声明,获取其标识符,并查看该标识符是否与"main"一致,如果有就抛出一个Path-insensitive的报告

需要注意的是,与路径敏感的checker不同,仅语法的检查器没有方便的CheckerContext使用,因此需要直接访问BugReporter对象,并且在获取必要的源代码位置付出一些努力

void WalkAST::VisitCallExpr(const CallExpr *CE)
{
if(const FunctionDecl *FD = CE->getDirectCallee())
if(const IdentifierInfo *II=FD->getIdentifier())
if(II->isStr(" main "))
{
SourceRange R = CE - > getSourceRange ();
PathDiagnosticLocation ELoc =
PathDiagnosticLocation::createBegin(CE,BR.getSourceManager(),ADC);
BR.EmitBasicReport(ADC->getDecl(),"Call to main","Example checker","Call to main",ELoc,R);
}
VisitChildren ( CE );
}

以上就是visitor的实现

我们只需要创建它并给他一些代码来实现它,因为上面的所有代码都都只是编写好了,但是没有被调用起来,所以需要编写check::ASTCodeBody回调函数

namespace {
class MainCallCheckerAST : public Checker<check::ASTCodeBody>
{
public:
void checkASTCodeBody(const Decl*D,AnalysisManager &AM,BugReporter &B) const;
}
}

 

void MainCallCheckerAST::checkASTCodeBody(const Decl*D,AnalysisManager &AM,
BugReporter &B)const
{
WalkAST Walker(BR,AM.getAnalysisDeclContext(D));
Walker.Visit(D->getBody());
}

通过这种方式,访问者从表示函数体的复合语句开始,然后下降到子语句中实施

Checker写好了,但是我们写的是通过AST来检测的,所以只能检测有没有main()这样的函数调用,而不能检测到函数指针调用main,这是AST的原因

(合并申明和实现)Merging statement and declaration visitors

有时想把申明和实现放在一起,就可以用多继承来实现

class WalkAST:public ConstStmtVisitor<WalkAST>,public ConstDeclVisitor<WalkAST>
{
public:
using ConstStmtVisitor<WalkAST>::Visit;
using ConstDeclVisitor<WalkAST>::Visit;
};

 

AST matchers

AST matchers是在clang AST中用来查找简单代码模式的新API,他们允许为这些模式编写极其简洁的声明性定义,几乎和用自然语言用文字描述它们一样简短-并为对发现的每个模式采取行动提供了一个接口,但是由于简单的代码模式、简单性和代码可读性导致AST matcher并不像AST visitors那样无所不能

实现一个简单的AST matchers(implementing a simple AST matchers)

这里我们可以重写最开始的alpha.core.MainCallChecker的checker来举例

检查器需要查找名为"main"的函数调用,那么就可以编写一个匹配器来查找这样的调用

callExpr(callee(functionDecl(hasName("main")))).bind("call")

完整的检查器逻辑现在只需要一行代码,剩下的就是写出checker实施和抛出错误报告,需要注意的是bind(...)指令会给AST节点分配名字作为后面调用

首先我们需要定义一个matcher的回调函数,每当这个matcher找到问题的时候回调函数都会被调用

Matcher回调函数需要继承MatcherFinder::MatchCallBack并实现run()方法

namespace {
class Callback : public MatchFinder::MatchCallback {
BugReporter &BR ;
AnalysisDeclContext*ADC ;
public :
void run(const MatchFinder::MatchResult & Result);
Callback(BugReporter &Reporter,AnalysisDeclContext *Context):BR(Reporter),ADC( Context ){}
};
}

理想情况下,match的回调函数唯一需要做的就是抛出基本的bug报告,但是有时matcher并不能覆盖整个checker逻辑,而很自然地将一些最终检查留给回调函数处理

 

void Callback::run(const MatchFinder::MatchResult &Result)
{
const CallExpr *CE=Result.Nodes.getStmtAs<CallExpr>("call");
assert(CE);
SourceRange R = CE->getSourceRange();
PathDiagnosticLocation ELoc =
PathDiagnosticLocation::createBegin(CE,BR.getSourceManager(),ADC);
BR.EmitBasicReport(ADC->getDecl(),"Call to main","Example checker","Call to main", ELoc,R);
}

在这个回调函数里面,我们通过前面用bind(...)函数定义的名字来获得函数的调用表达式

查看matcher,我们可以确定这样的调用表达式是存在的,所以可以肯定的是通过名称获得了语句

定义检查器,尝试翻译整个匹配

namespace {
class MainCallCheckerMatchers:public Checker<check::EndOfTranslationUnit>
{
public:
void checkEndOfTranslationUnit(const TranslationUnitDecl *TU,
AnalysisManager &AM,BugReporter &B)const ;
};
}

最后在checker回调中,需要构造matcher并使用它查找bug

void MainCallCheckerMatchers::checkEndOfTranslationUnit(
const TranslationUnitDecl *TU,AnalysisManager &AM,BugReporter &B) const
{
MatchFinder F;
Callback CB(B,AM.getAnalysisDeclContext(TU));
F.addMatcher(stmt(hasDescendant(callExpr(callee(functionDecl(hasName("main")))).bind ("call"))) ,&CB);
F.matchAST(AM.getASTContext());
}

这里的MatchFinder下的matchAST()函数会检查单元的整个AST,检查完毕输出类似于checker的visitor版本

我们从AnalysisManager获得的ASTContext结构包含程序的整个AST,以及有关AST的各种信息

再次使用matchers(Re-using matchers)

如果某个子模式在matcher中重复多次,则可以存储并重复使用它

TypeMatcher TypeM = templateSpecializationType().bind("type");
DeclarationMatcher VarDeclM =varDecl(hasType(TypeM)).bind("decl");
StatementMatcher TempObjM = temporaryObjectExpr(hasType(TypeM)).bind("stmt");

这里的TypeM被存储,然后又在另外两个matcher中重复使用两次,然后这里的两个matcher又被存储起来

定义自定义matcher(Defining custom matchers)

有时组合预定义的匹配器不足以实现所需的检查,这种情况下实现自定义的AST matcher就很关键了

实现AST matcher只需要几行代码,在ASTMatcher.h可以找到很多例子

在checker中实现自定义AST matcher时,只需要把它放到 clang::ast_matchers namespace里面就好

例如:

namespace clang {
namespace ast_matchers {
AST_MATCHER (RecordDecl,isUnion){
return Node.isUnion();
}  
?
} //这里在clang的命名空间中的ast_matchers来处理
}

这个例子定义了一个自定义的matcher

匹配特定语句(Matching particular statements)

MatchFinder的matchAST()方法匹配了整个的AST

如果你想要匹配一部分的AST你可以用match()来处理

例如:用check::ASTCodeBody写一个MainCallChecker,需要把D->getBody()和MatchFinder配对

然而,match(...)和matchAST(...)语义有区别:match尝试匹配语句本身,而matchAST尝试匹配子语句

所以需要修改matcher,使其手动查找子语句

void MainCallCheckerMatchers::checkASTCodeBody(const Decl *D,AnalysisManager &AM,BugReporter &BR) const
{
MatchFinder F ;
Callback CB(BR,AM.getAnalysisDeclContext(D));
  F.addMatcher(stmt(hasDescendant(callExpr(callee(functionDecl(hasName("main")))).bind ("call"))),&CB);
F.matchAST(*(D->getBody()),AM.getASTContext());
}

常量折叠(Constant folding)

从一个表达式的AST来看,这个表达式实际的值是一个常量值,为了方便可以直接把表达式的值对于修改为具体的值。有一个现场的解决方案,使用Expr的evaluatesint(...)方法可以处理

const Expr *E=/* some AST expression you are interested in */
llvm :: APSInt Result;
if(E->EvaluateAsInt(Result,ACtx,Expr::SE_AllowSideEffects)) {
/* we managed to obtain the value of the expression */
uint64_t IntResult =Result.getLimitedValue();
/* ... */
} else {
/* the expression doesn ’t fold to into a constant value */
}

 

Clang Static Analyzer-使用手册-基于AST的Checkers

原文:https://www.cnblogs.com/Sna1lGo/p/14649672.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!