ClangTool
相关源码与使用
核心方法与入口源码
int ClangTool::run (ToolAction * Action)
在命令行中指定的所有文件上运行一个操作。
返回值0表示成功;1表示发生任何错误;2表示没有错误,但由于缺少编译命令,一些文件被跳过。
ClangTool::run
方法内部使用ToolInvocation
ToolInvocation Invocation(std::move(CommandLine), Action, Files.get(), PCHContainerOps);
Invocation.run()
Invocation.run()内部调用
Action->runInvocation(std::move(Invocation)
, Files
, std::move(PCHContainerOps)
, DiagConsumer);
bool FrontendActionFactory::runInvocation(
std::shared_ptr<CompilerInvocation> Invocation, FileManager *Files,
std::shared_ptr<PCHContainerOperations> PCHContainerOps,
DiagnosticConsumer *DiagConsumer) {
// Create a compiler instance to handle the actual work.
CompilerInstance Compiler(std::move(PCHContainerOps));
Compiler.setInvocation(std::move(Invocation));
Compiler.setFileManager(Files);
// The FrontendAction can have lifetime requirements for Compiler or its
// members, and we need to ensure it's deleted earlier than Compiler. So we
// pass it to an std::unique_ptr declared after the Compiler variable.
std::unique_ptr<FrontendAction> ScopedToolAction(create());
// Create the compiler's actual diagnostics engine.
Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
if (!Compiler.hasDiagnostics())
return false;
Compiler.createSourceManager(*Files);
const bool Success = Compiler.ExecuteAction(*ScopedToolAction);
Files->clearStatCache();
return Success;
}
其中调用
bool CompilerInstance::ExecuteAction(FrontendAction &Act)
ToolAction
处理clang::CompilerInvocation
的接口
clang::CompilerInvocation
用于保存调用编译器所需数据的辅助类。
这个类被设计用来表示编译器的抽象 “调用”,包括诸如包含路径、代码生成选项、警告标志等数据。
clang::FrontendAction
可由前端执行的动作的抽象基类。
clang::FrontendAction
的继承关系图
clang::ASTFrontendAction
——用于基于AST消费者的前台操作clang::ASTMergeAction
——将AST合并在一起clang::PreprocessorFrontendAction
——基于预处理器的前台操作clang::ReadPCHAndPreprocessAction
——基于预处理器的前端动作,也可以加载PCH文件clang::WrapperFrontendAction
clang::PrintDependencyDirectivesSourceMinimizerAction
clang::PrintPreambleAction
clang::DumpCompilerOptionsAction
clang::InitOnlyAction
clang::FrontendAction
里可以override
的一些方法
//通过在已经初始化的AST消费者上运行Sema,实现ExecuteAction接口
void ExecuteAction () override
//准备在给定的CompilerInstance上执行动作
virtual bool PrepareToExecuteAction (CompilerInstance &CI)
//当MyFrontendAction构建 AST 树后会调用 CreateASTConsumer来使用我们客制化实现的 ASTConsumer,并将相关节点返回给我们
virtual std::unique_ptr<ASTConsumer> CreateASTConsumer (CompilerInstance &CI, StringRef InFile)=0
// 在开始处理单一输入之前的回调,让人有机会在BeginSourceFileAction被调用之前修改CompilerInvocation或做一些其他动作
virtual bool BeginInvocation (CompilerInstance &CI)
//在处理单一输入的开始时回调
virtual bool BeginSourceFileAction (CompilerInstance &CI)
//在处理单个输入结束时的回调
//比如文档里给出的例子是当处理输入结束时,向控制台输出程序代码
virtual void EndSourceFileAction ()
//在处理单个输入结束时回调,以确定是否应删除输出文件
virtual bool shouldEraseOutputFiles ()
clang::WrapperFrontendAction
一个前台动作,它简单地包装了一些其他运行时指定的前台动作。从这个类派生出来的动作可以围绕一些现有的动作的行为注入自定义逻辑。它通过转发到被包装的动作来实现FrontendAction
接口中的每个虚拟方法。
clang::tooling::FrontendActionFactory
产生clang::FrontendActions
的接口。
拥有一个工厂接口为ClangTool
处理的每个翻译单元创建一个新的FrontendAction
。这个类也是一个ToolAction
,它使用create()
创建的FrontendActions
来处理每个翻译单元。
使用
主入口
MyFrontendAction
可以理解为一个我们需要自己做的操作
std::string const clang_inc_dir1("-I/home/admin/Programs/llvm-11.0.0.install/lib/clang/11.0.0/include");
static llvm::cl::OptionCategory MyToolCategory("my-tool options");
void add_include_paths(ClangTool& tool)
{
// add header search paths to compiler
ArgumentsAdjuster ardj1 = getInsertArgumentAdjuster(clang_inc_dir1.c_str());
tool.appendArgumentsAdjuster(ardj1);
return;
}
int main(int argc, const char* argv[])
{
auto ExpectedParser = CommonOptionsParser::create(argc, argv, MyToolCategory, llvm::cl::OneOrMore);
if (!ExpectedParser) {
// Fail gracefully for unsupported options.
llvm::errs() << ExpectedParser.takeError();
return 1;
}
CommonOptionsParser& OptionsParser = ExpectedParser.get();
ArgumentsAdjuster ardj1 = getInsertArgumentAdjuster(clang_inc_dir1.c_str());
ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());
Tool.appendArgumentsAdjuster(ardj1);
add_include_paths(Tool);
return Tool.run(newFrontendActionFactory<MyFrontendAction>().get());;
}
MyFrontendAction
可以继承任何ToolAction
的子类。如果以AST树的操作为主,则为ASTFrontendAction
class MyFrontendAction : public ASTFrontendAction
{
public:
MyFrontendAction() = default;
void EndSourceFileAction() override;
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance& CI, StringRef file) override;
private:
//MyFrontendAction 中可以发现有一个 TheRewriter 成员,这是一个重写器,主要是用来将我们 if else 添加完注释的代码进行回写
Rewriter TheRewriter;
};
下面是MyFrontendAction
的一种具体实现
std::unique_ptr<ASTConsumer> MyFrontendAction::CreateASTConsumer(CompilerInstance& CI, StringRef file)
{
llvm::errs() << "** Creating AST consumer for: " << file << "\n";
TheRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts());
// 获取ASTContext的办法
clang::ASTContext& astContext = CI.getASTContext();
// just for test
astContext.PrintStats();
return std::make_unique<MyASTConsumer>(TheRewriter);
}
// 在处理单个输入结束时回调
void MyFrontendAction::EndSourceFileAction()
{
SourceManager& SM = TheRewriter.getSourceMgr();
llvm::errs() << "** EndSourceFileAction for: " << SM.getFileEntryForID(SM.getMainFileID())->getName() << "\n";
// Now emit the rewritten buffer.
TheRewriter.getEditBuffer(SM.getMainFileID()).write(llvm::outs());
}
在CreateASTConsumer
里使用Matcher的示例代码思路
代码来自
class MyFrontendAction : public ASTFrontendAction
{
public:
MyFrontendAction() = default;
void EndSourceFileAction() override
{
auto m = getCompilerInstance().getDiagnostics().getNumWarnings();
spdlog::info("{} Warning\n", m);
}
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance& CI, StringRef file) override
{
llvm::errs() << "** Creating AST consumer for: " << file << "\n";
auto m = CI.getDiagnostics().getNumWarnings();
spdlog::info("{}", m);
auto FuncDeclMatcher =
functionDecl(isExpansionInMainFile(),
anyOf(hasAncestor(cxxRecordDecl().bind("methodclass")), unless(hasAncestor(cxxRecordDecl()))),
anyOf(forEachDescendant(callExpr().bind("callExprFunction")),
unless(forEachDescendant(callExpr().bind("callExprFunction")))))
.bind("FunctiondFeclWithCall");
Finder.addMatcher(FuncDeclMatcher, &FuncCall);
return Finder.newASTConsumer();
}
private:
Func_Call FuncCall;
MatchFinder Finder;
};
其中的MyASTConsumer
继承自ASTConsumer
可以对AST中的节点进行自定义处理
比如HandleTopLevelDecl
,处理指定的顶层声明,会回调给我们相应的节点信息,使用 MyASTVisitor
来实现我们想要的功能即可
class MyASTConsumer : public ASTConsumer
{
public:
MyASTConsumer(Rewriter& R) : Visitor(R) {}
// Override the method that gets called for each parsed top-level
// declaration.
bool HandleTopLevelDecl(DeclGroupRef DR) override
{
for (auto& b : DR)
{
// Traverse the declaration using our AST visitor.
Visitor.TraverseDecl(b);
b->dump();
}
return true;
}
private:
MyASTVisitor Visitor;
};
然后定义MyASTVisitor
来访问AST
class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor>
{
public:
MyASTVisitor(Rewriter& R) : TheRewriter(R) {}
bool VisitStmt(Stmt* s)
{
// Only care about If statements.
if (isa<IfStmt>(s))
{
auto* IfStatement = cast<IfStmt>(s);
Stmt* Then = IfStatement->getThen();
TheRewriter.InsertText(Then->getBeginLoc(), "// the 'if' part\n", true, true);
Stmt* Else = IfStatement->getElse();
if (Else)
TheRewriter.InsertText(Else->getBeginLoc(), "// the 'else' part\n", true, true);
}
return true;
}
bool VisitFunctionDecl(FunctionDecl* f)
{
// Only function definitions (with bodies), not declarations.
if (f->hasBody())
{
Stmt* FuncBody = f->getBody();
// Type name as string
QualType QT = f->getReturnType();
std::string TypeStr = QT.getAsString();
// Function name
DeclarationName DeclName = f->getNameInfo().getName();
std::string FuncName = DeclName.getAsString();
// Add comment before
std::stringstream SSBefore;
SSBefore << "// Begin function " << FuncName << " returning " << TypeStr << "\n";
SourceLocation ST = f->getSourceRange().getBegin();
TheRewriter.InsertText(ST, SSBefore.str(), true, true);
// And after
std::stringstream SSAfter;
SSAfter << "\n// End function " << FuncName;
ST = FuncBody->getEndLoc().getLocWithOffset(1);
TheRewriter.InsertText(ST, SSAfter.str(), true, true);
}
return true;
}
private:
Rewriter& TheRewriter;
};
如何获取ASTContext
- 在调用
CreateASTConsumer
时,ASTContext
可以从CompilerInstance
里的getASTContext()
方法获得。 - 继承了
clang::FrontendAction
的类可以通过getCompilerInstance()
来获得CompilerInstance
,然后通过getASTContext()
方法获得。
std::unique_ptr<ASTConsumer> MyFrontendAction::CreateASTConsumer(CompilerInstance& CI, StringRef file)
{
// 获取ASTContext的办法
clang::ASTContext& astContext = CI.getASTContext();
}
void MyFrontendAction::EndSourceFileAction()
{
getCompilerInstance().getASTContext();
}
关于ClangTool::run
是否支持多线程
CompilerInstance &getCompilerInstance() const {
assert(Instance && "Compiler instance not registered!");
return *Instance;
}
Instance
为FrontendAction
所持有的一个成员变量
class FrontendAction {
CompilerInstance *Instance;
// 其他内容略
}
对于不同的FrontendAction
而言,其所持有的CompilerInstance
是不一样的,互不影响
因此只需要new不同的FrontendAction
,目前推测是可以支持多线程的
整体流程
ClangTool::run
传入ToolAction
,Action作为一个执行动作- 定义一个自己的类
MyFrontendAction
,继承自FrontendAction
,代表需要执行的操作 - 在自己的类
MyFrontendAction
中override一些FrontendAction
需要重新定义的方法,其中CreateASTConsumer
是为了实现自定义操作必须要override的一个方法 - 定义一个自己的类
MyASTConsumer
,继承自ASTConsumer
,以此来使用一些已有的遍历功能,比如HandleTopLevelDecl
实现从上到下的遍历 - 最后定义一个自己的类
MyASTVisitor
,继承自RecursiveASTVisitor
,然后在MyASTVisitor
里实现自己的操作