ClangTool相关源码与使用

ClangTool相关源码与使用

核心方法与入口源码

int ClangTool::run	(ToolAction * Action)	

在命令行中指定的所有文件上运行一个操作。

返回值0表示成功;1表示发生任何错误;2表示没有错误,但由于缺少编译命令,一些文件被跳过。

ClangTool::run方法内部使用ToolInvocation

Tooling.cpp第573行

ToolInvocation Invocation(std::move(CommandLine), Action, Files.get(), PCHContainerOps);

Invocation.run()

Invocation.run()内部调用

Tooling.cpp第398行

Action->runInvocation(std::move(Invocation)
, Files
, std::move(PCHContainerOps)
, DiagConsumer);

Tooling.cpp第402行

 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;
 }

其中调用

CompilerInstance.cpp第989行

bool CompilerInstance::ExecuteAction(FrontendAction &Act)

ToolAction

处理clang::CompilerInvocation的接口

clang::CompilerInvocation

用于保存调用编译器所需数据的辅助类。
这个类被设计用来表示编译器的抽象 “调用”,包括诸如包含路径、代码生成选项、警告标志等数据。

clang::FrontendAction

https://clang.llvm.org/doxygen/classclang_1_1tooling_1_1ToolAction__inherit__graph.png

可由前端执行的动作的抽象基类。

clang::FrontendAction的继承关系图
https://clang.llvm.org/doxygen/classclang_1_1FrontendAction__inherit__graph.png

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的示例代码思路

代码来自

四,Clang ASTMatcher基础学习

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

  1. 在调用CreateASTConsumer时,ASTContext可以从CompilerInstance里的getASTContext()方法获得。
  2. 继承了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是否支持多线程

FrontendAction第119行

   CompilerInstance &getCompilerInstance() const {
     assert(Instance && "Compiler instance not registered!");
     return *Instance;
   }

InstanceFrontendAction所持有的一个成员变量

class FrontendAction {
   CompilerInstance *Instance;
   // 其他内容略
}

对于不同的FrontendAction而言,其所持有的CompilerInstance是不一样的,互不影响

因此只需要new不同的FrontendAction,目前推测是可以支持多线程的

整体流程

  1. ClangTool::run传入ToolAction,Action作为一个执行动作
  2. 定义一个自己的类MyFrontendAction,继承自FrontendAction,代表需要执行的操作
  3. 在自己的类MyFrontendAction中override一些FrontendAction需要重新定义的方法,其中CreateASTConsumer是为了实现自定义操作必须要override的一个方法
  4. 定义一个自己的类MyASTConsumer,继承自ASTConsumer,以此来使用一些已有的遍历功能,比如HandleTopLevelDecl实现从上到下的遍历
  5. 最后定义一个自己的类MyASTVisitor,继承自RecursiveASTVisitor,然后在MyASTVisitor里实现自己的操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值