Clang教程之实现源源变化(5)
其实我也没想到会有这一节。一直有人在说AST上只有抽象的语法结构,没有CFG信息,不能实现某某功能等等,但就实际来说,目前的clang上边,通过AST的Anslysis也能实现一些控制流相关的东西,确实没有IR上进行比较方便和功能丰富。
先介绍下这一节要用到的一个库CFG,在clang/lib/Analysis文件夹下边,在这个文件夹下,除了CFG还有可达分析(ReachabilityAnalysis)、活跃变量(LiveVariable)、ThreadSafety和未初始化(UninitizedValues)分析等工具,本版本(13.0)功能相比之前4.0版本有了非常大的提升。
先介绍下使用的测试代码,其实是从Clang的UserManual上拷过来的。https://clang.llvm.org/docs/InternalsManual.html#basic-blocks
1 int foo(int x) { 2 x = x + 1; 3 if (x > 2) 4 x++; 5 else { 6 x += 2; 7 x *= 2; 8 } 9 10 return x; 11 }View Code
其对应AST为
从CFG(这里提供了一个CFGBlock的功能)的角度来说,因为是foo函数,可以大胆猜一下,肯定会有一个函数入口,一个函数出口,然后函数体里边有一个“x = x + 1;”的Block块,然后if/else的TrueBody和FalseBody又是两个Block,可能还有一个条件判断的Block(也可能是条件判断和“x = x +1;”这个Block合并到了一起)。
下边给出主要的实现代码:
1 //------------------------------------------------------------------------------ 2 // Tooling sample. Demonstrates: 3 // 4 // CFG Demo to show how to use CFG 5 // 6 // jourluohua (jourluohua@sina.com) 7 // This code is in the public domain 8 //------------------------------------------------------------------------------ 9 #include <sstream> 10 #include <string> 11 #include <map> 12 13 #include "clang/AST/AST.h" 14 #include "clang/AST/ASTConsumer.h" 15 #include "clang/AST/RecursiveASTVisitor.h" 16 #include "clang/Frontend/ASTConsumers.h" 17 #include "clang/Frontend/CompilerInstance.h" 18 #include "clang/Frontend/FrontendActions.h" 19 #include "clang/Rewrite/Core/Rewriter.h" 20 #include "clang/Tooling/CommonOptionsParser.h" 21 #include "clang/Tooling/Tooling.h" 22 #include "llvm/Support/raw_ostream.h" 23 #include "clang/Analysis/CFG.h" 24 #include "clang/Analysis/CFGStmtMap.h" 25 #include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h" 26 #include "clang/Analysis/Analyses/CalledOnceCheck.h" 27 #include "clang/Analysis/Analyses/Consumed.h" 28 #include "clang/Analysis/Analyses/ReachableCode.h" 29 #include "clang/Analysis/Analyses/ThreadSafety.h" 30 #include "clang/Analysis/Analyses/UninitializedValues.h" 31 #include "clang/Analysis/AnalysisDeclContext.h" 32 33 using namespace clang; 34 using namespace clang::driver; 35 using namespace clang::tooling; 36 37 38 static llvm::cl::OptionCategory ToolingSampleCategory("Tooling Sample"); 39 40 // Implementation of the ASTConsumer interface for reading an AST produced 41 // by the Clang parser. 42 class MyASTConsumer : public ASTConsumer { 43 public: 44 MyASTConsumer(Rewriter &R) { 45 } 46 47 // Override the method that gets called for each parsed top-level 48 // declaration. 49 bool HandleTopLevelDecl(DeclGroupRef DR) override { 50 for (DeclGroupRef::iterator b = DR.begin(), e = DR.end(); b != e; ++b) { 51 // Traverse the declaration using our AST visitor. 52 (*b)->dump(); 53 // Construct the analysis context with the specified CFG build options. 54 AnalysisDeclContext AC(/* AnalysisDeclContextManager */ nullptr, *b); 55 // Don't generate EH edges for CallExprs as we'd like to avoid the n^2 56 // explosion for destructors that can result and the compile time hit. 57 if(&AC == nullptr){ 58 continue; 59 } 60 AC.getCFGBuildOptions().PruneTriviallyFalseEdges = true; 61 AC.getCFGBuildOptions().AddEHEdges = false; 62 AC.getCFGBuildOptions().AddInitializers = true; 63 AC.getCFGBuildOptions().AddImplicitDtors = true; 64 AC.getCFGBuildOptions().AddTemporaryDtors = true; 65 AC.getCFGBuildOptions().AddCXXNewAllocator = false; 66 AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true; 67 68 AC.getCFGBuildOptions().setAllAlwaysAdd(); 69 70 if(CFG *cfg = AC.getCFG()){ 71 cfg->dump(LangOptions(), false); 72 } 73 } 74 return true; 75 } 76 77 private: 78 }; 79 80 // For each source file provided to the tool, a new FrontendAction is created. 81 class MyFrontendAction : public ASTFrontendAction { 82 public: 83 MyFrontendAction() {} 84 void EndSourceFileAction() override { 85 SourceManager &SM = TheRewriter.getSourceMgr(); 86 llvm::errs() << "** EndSourceFileAction for: " 87 << SM.getFileEntryForID(SM.getMainFileID())->getName() << "\n"; 88 89 // Now emit the rewritten buffer. 90 TheRewriter.getEditBuffer(SM.getMainFileID()).write(llvm::outs()); 91 } 92 93 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, 94 StringRef file) override { 95 llvm::errs() << "** Creating AST consumer for: " << file << "\n"; 96 TheRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts()); 97 return std::make_unique<MyASTConsumer>(TheRewriter); 98 } 99 100 private: 101 Rewriter TheRewriter; 102 }; 103 104 int main(int argc, const char **argv) { 105 llvm::Expected<CommonOptionsParser> op=CommonOptionsParser::create(argc, argv, ToolingSampleCategory); 106 107 ClangTool Tool(op.get().getCompilations(), op.get().getSourcePathList()); 108 109 // ClangTool::run accepts a FrontendActionFactory, which is then used to 110 // create new objects implementing the FrontendAction interface. Here we use 111 // the helper newFrontendActionFactory to create a default factory that will 112 // return a new MyFrontendAction object every time. 113 // To further customize this, we could create our own factory class. 114 return Tool.run(newFrontendActionFactory<MyFrontendAction>().get()); 115 }
从代码中可以看到主要修改的是DeclRef的循环处理部分,大概流程分为以下几步:
1. 新建一个针对Decl*的AnalysisDeclContext
2. 添加针对分析的Options(尤其是setAllAlwaysAdd)
3. 得到针对这个Decl的CFG
最后得到的CFG如下: