AST Matcher
ASTMatcher 是 Clang 中用来帮助我们实现 code-to-code 的转译或者完成某些查询的工具。在深入介绍ASTMatcher 前,我们需要先介绍一下Clang中的AST。
AST Matcher简介
ASTMatcher提供了一个领域特定语言(DSL)来创建基于Clang AST的谓词,同时支持C++,这意味着允许用户编写一个程序来匹配AST节点并能通过访问节点的c++接口来获取该AST节点的属性、源位置等任何信息。
其主要由宏与模板驱动,用法和 函数式编程 及其类似。
ASTMatcher用来匹配AST的节点,它通过调用构造函数创建,也可以构建成一个ASTMatchers的树,其内部可以嵌套多个ASTMatcher,使得匹配更加具体准确。
所有匹配器都是名词描述实体并且可以绑定,这样它们就会指向匹配到的内容。为此,只需要在这些匹配器中调用 bind() 方法,例如:
variable(hasType(isInteger())).bind("intvar")
创建ASTMatcher
由于Clang AST中有上千个class,我们显然不可能一个个去看去分析。
这时候我们要清楚一点:使用ASTMatcher的前提是了解你想匹配的AST的样子。
通常情况下,创建合适的ASTMatcher的策略如下:
- 寻找想匹配的节点的最外层的类
- 在 AST Matcher Reference 中查看所写的Matcher要么匹配到需要的节点,要么进行"细化"处理
- 创建外部匹配表达式,验证它是否按预期运行。
- 为接下来你想匹配的内部节点检查匹配器。
- 重复以上步骤,直到完成匹配器。
在我们的项目中,我们采取由一个简单的.c例子入手,观察它的AST语法树,进而总结全局变量的特性这样的一种策略。
首先,.c例子如下:
#include<stdio.h>
int a;
int main(){
a = 1;
return 0;
}
调用clang -cc1 -ast-dump查看其语法树如下:
|-VarDecl 0xf4a0b8 <F:\1.c:2:1, col:5> col:5 used a 'int'
`-FunctionDecl 0xf4a160 <line:3:1, line:6:1> line:3:5 main 'int ()'
`-CompoundStmt 0xf4a248 <col:11, line:6:1>
|-BinaryOperator 0xf4a200 <line:4:1, col:5> 'int' '='
| |-DeclRefExpr 0xf4a1c8 <col:1> 'int' lvalue Var 0xf4a0b8 'a' 'int'
| `-IntegerLiteral 0xf4a1e0 <col:5> 'int' 1
`-ReturnStmt 0xf4a238 <line:5:1, col:8>
`-IntegerLiteral 0xf4a218 <col:8> 'int' 0
我们可以清楚的看到,全局变量a对应的节点类型为 VarDecl ,引用该变量处的节点类型为 DeclRefExpr ,而DeclRefExpr 最外层有一层函数,对应 FunctionDecl节点类型。
得到这些信息,我们就可以总结出来"匹配模型"。
对于使用了的全局变量,我们找它的引用,这个引用需要对应于一个全局变量声明,而且引用是在某个函数内部。这种模式下,我们即可得到所有已使用了的全局变量的信息,包括在哪个函数内部调用。
转换到AST 节点来看:它首先是一个DeclRefExpr类型节点,同时它对应于一个VarDecl全局节点,而且这个DeclRefExpr节点在某个FunctionnDecl下。
因此,我们写出如下的Matcher:
StatementMatcher GlobalVarMatcher = declRefExpr(
to(
varDecl(
hasGlobalStorage()
).bind("gvarName")
) // to
, hasAncestor(
functionDecl().bind("function")
)
).bind("globalReference");
在上述Matcher中,为匹配特定AST节点,我们把匹配的varDecl节点绑定到字符串“gvarName”,functionDecl节点绑定到字符串"function",declRefExpr节点绑定到字符串"globalReference",以便稍后在匹配回调中检索。
获取匹配节点
定义了matcher后将需要添加更多的工具来运行它们。Matchers与MatchCallback配对,并注册一个MatchFinder对象,然后从一个ClangTool运行。
matcher回调中我们需要对输入源代码进行更改。接下来,我们将使用在前面步骤中绑定的节点。
MatchFinder::run()回调使用一个MatchFinder:: matchresult& 作为它的参数。我们最感兴趣的是节点成员,以及如何检索它们。
由于我们绑定了三个节点(由“gvarName”、"function"和"globalReference"标识),我们可以通过使用getNodeAs()成员函数获得匹配的节点。
代码如下:
class Global_Printer : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result)
{
FunctionDecl const * func_decl =
Result.Nodes.getNodeAs<FunctionDecl>("function");
Expr const * g_var = Result.Nodes.getNodeAs<Expr>("globalReference");
VarDecl const * var = Result.Nodes.getNodeAs<VarDecl>("gvarName");
if (func_decl && var) {
/*
DeclarationNameInfo NameInfo = func_decl->getNameInfo();
DeclarationName Name = NameInfo.getName();
*/
cout << "变量名:";
DeclarationName Name2 = var->getDeclName();
cout << var->getNameAsString() << "\n";
int paraNum = func_decl->getNumParams();
cout << "全局变量类型:" << var->getType().getAsString() << " ";
cout << "所在函数:" << func_decl->getCallResultType().getAsString() << " " << func_decl->getNameAsString();
cout << "(";
if (paraNum != 0)
{
for (int i = 0; i < paraNum; i++)
{
if (i != 0) cout << ",";
cout << func_decl->getParamDecl(i)->getType().getAsString() << " ";
cout << func_decl->getParamDecl(i)->getNameAsString();
}
}
cout << ")" << endl;
//Name2.dump();
}
}
};
int main(int argc, const char **argv) {
CommonOptionsParser OptionsParser(argc, argv, MyToolCategory);
ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());
Global_Printer GvarPrinter;
MatchFinder Finder;
Finder.addMatcher(GlobalVarMatcher, &GvarPrinter);
freopen("F://out.txt", "w", stdout);
Tool.run(newFrontendActionFactory(&Finder).get());
Finder.~MatchFinder();
return 0;
}
完整代码
#include <iostream>
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include <fstream>
using namespace std;
using namespace clang::tooling;
using namespace llvm;
static llvm::cl::OptionCategory MyToolCategory("global-detect options");
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
using namespace clang;
using namespace clang::ast_matchers;
StatementMatcher GlobalVarMatcher = declRefExpr(
to(
varDecl(
hasGlobalStorage()
).bind("gvarName")
) // to
, hasAncestor(
functionDecl().bind("function")
)
).bind("globalReference");
class Global_Printer : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result)
{
FunctionDecl const * func_decl =
Result.Nodes.getNodeAs<FunctionDecl>("function");
Expr const * g_var = Result.Nodes.getNodeAs<Expr>("globalReference");
VarDecl const * var = Result.Nodes.getNodeAs<VarDecl>("gvarName");
if (func_decl && var) {
/*
DeclarationNameInfo NameInfo = func_decl->getNameInfo();
DeclarationName Name = NameInfo.getName();
*/
cout << "变量名:";
DeclarationName Name2 = var->getDeclName();
cout << var->getNameAsString() << "\n";
int paraNum = func_decl->getNumParams();
cout << "全局变量类型:" << var->getType().getAsString() << " ";
cout << "所在函数:" << func_decl->getCallResultType().getAsString() << " " << func_decl->getNameAsString();
cout << "(";
if (paraNum != 0)
{
for (int i = 0; i < paraNum; i++)
{
if (i != 0) cout << ",";
cout << func_decl->getParamDecl(i)->getType().getAsString() << " ";
cout << func_decl->getParamDecl(i)->getNameAsString();
}
}
cout << ")" << endl;
//Name2.dump();
}
}
};
int main(int argc, const char **argv) {
CommonOptionsParser OptionsParser(argc, argv, MyToolCategory);
ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());
Global_Printer GvarPrinter;
MatchFinder Finder;
Finder.addMatcher(GlobalVarMatcher, &GvarPrinter);
freopen("F://out.txt", "w", stdout);
Tool.run(newFrontendActionFactory(&Finder).get());
Finder.~MatchFinder();
return 0;
}
作者:whd_Alive
链接:
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。