优秀的编程知识分享平台

网站首页 > 技术文章 正文

基于Clang库实现C++文件修改

nanyue 2025-01-20 15:36:45 技术文章 4 ℃



Clang通过两种方式访问AST(抽象语法树)

  • Visitor遍历模式
  • Matcher匹配模式

Visitor遍历模式


初始化CompilerInstance之后,调用其成员函数ExcutionAction, ExcutionAction会间接依次调用FrontendAction的6个成员函数(直接调用的是FrontendAction的三个public 接口,BeginSourceFile,Execute,EndSourceFile),而FrontendAction的ExecuteAction会最终调用语法分析函数ParseAST(未强制要求ParseAST放入ExcuteAction,但ASTFrontendAction如此)。 ParseAST在分析过程中,又会调用ASTConsumer的多个函数(用得最多是HandleTopLevelDecl和 HandleTranslationUnit)。FrontendAction是文件级别的动作,ASTConsumer则与一个Translation Unit内部处理过程相关。RecursiveASTVisitor是针对AST node的遍历,一般需要ASTConsumer中呈现的AST node(如TranslationUnitDecl)作为参数。  

深度优先的方式遍历整个 Clang AST 并访问每个节点的一个类。其执行三种不同的操作:

  1. 遍历整个 AST(即访问每个节点)
  2. 对于给定的一个节点,沿着其类继承关系向前(Derived->Base方向)游历,直至到达一个顶层类(如 Stmt,Decl,Type)
  3. 对于一个给定的 (node, class) 组合,调用一个可由用户重写(user-overridable)的函数来访问该节点,其中 class 是 node 的动态类型的某个基类

这些操作由三组类方法来完成,分别是:

  1. TraverseDecl(Decl *x) 执行任务1,是遍历以x为根的 AST 的入口函数。该函数只是简单地将任务分派给 TraverseFoo(Foo *x),继而调用 WalkUpFromFoo(x),然后递归地访问x的子节点。TraverseFoo 中的 Foo 是 *x 的动态类型。TraverseDecl(Decl *x) 和 TraverseType(QualType x) 执行的操作类似
  2. WalkUpFromFoo(Foo *x) 执行任务2。该函数首先调用 WalkUpFromBar(x),然后调用 VisitFoo(x)。其中 Bar 是 Foo 的直接父类(direct parent)
  3. VisitFoo(Foo *x) 执行任务3

这三组方法具有下列层次关系:Traverse* > WalkUpFrom* > Visit*。某一层次的方法可以调用相同层次的另一个方法以及较低层次的方法,但不能调用层次比它高的方法。


Matcher匹配模式

ifStmt(hasCondition(expr().bind("C")), hasThen(stmt().bind("T")), hasElse(stmt().bind("E")))
  • ifStmt是一个匹配器实例,用来判断匹配当前节点是否为 if 节点,并且满足括号内的其他匹配器要求。他是一个包含 operator() 方法的类的实例,但是你也可以将其简单地看作是一个函数
  • expr 和 stmt:与 ifStmt 类似,也是匹配器的实例,用于匹配 Expression 和 Statement。
  • .bind(str):一些匹配器拥有的方法,它可以将被匹配的这个子表达式树绑定到 str 中。例如上文中,匹配成功后会将绑定到 C 上,我们后续可以通过 C 找到这个表达式。
  • hasConditon、hasThen、hasElse:它是一个作用于特定节点类型的匹配器,它尝试去获得节点的子树。


在 Clang 中,AST Matcher 被分为了三类:

  1. 节点匹配器(Node Matchers):用于匹配特定类型的节点,是匹配器的核心。同时,Node Matchers 是唯一具有 bind 方法的一类匹配器。例如 ifStmt、expr、classTemplateDecl 等。
  2. 窄化匹配器(Narrowing Matchers):对当前节点上的某些属性进行匹配,从而缩小当前类型的节点集的匹配范围。例如 isPrivate、equals、isConst 等。此外,有一些逻辑匹配器(allOf、anyOf、anything、unless),他们能构成更强大的匹配器。
  3. 遍历匹配器(Traversal Matchers):遍历匹配器指定从当前节点可到达的其他节点的关系。例如 hasCondition、hasParent、hasLHS 等。此外,有一些强大的匹配器 (has、hasDecendant、forEach)可以构成更强大的匹配器。

每一个匹配表达式都以 Node Matchers 开始,然后使用 Narrowing 或 Traversal Matcher 进一步完善。所有遍历匹配器都将 Node Matcher 作为其参数。此外,每个 Node Matcher 都隐含了一个 allOf Matcher。

实现

#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/CodeGen/ObjectFilePCHContainerOperations.h"
#include "clang/Driver/Options.h"
#include "clang/Rewrite/Frontend/FixItRewriter.h"
#include "clang/Rewrite/Frontend/FrontendActions.h"
#include "clang/StaticAnalyzer/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Syntax/BuildTree.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "clang/Tooling/Syntax/Tree.h"
#include "clang/Tooling/Tooling.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/TargetSelect.h"

using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::driver;
using namespace clang::tooling;
using namespace llvm;


static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
static cl::extrahelp MoreHelp(
    "\tFor example, to run rewriter\n"
    "\n");

static cl::OptionCategory ClangCheckCategory("rewriter options");
static const opt::OptTable &Options = getDriverOptTable();
static cl::opt<bool>
    VISITORDump("visitor",
            cl::desc(Options.getOptionHelpText(options::OPT_ast_dump)),
            cl::cat(ClangCheckCategory));


// Visitor 遍历模式
class ReWriteClassVisitor
    : public RecursiveASTVisitor<ReWriteClassVisitor> {
public:
  explicit ReWriteClassVisitor(ASTContext *context, SourceManager *sm, Rewriter *rw)
      : context_(context), sm_(sm), rewriter_(rw) {}

    bool VisitStmt(Stmt *s) {
        // 当前文件,排除包含文件
      if (sm_->isInMainFile(s->getBeginLoc()) && isa<IfStmt>(s)) {
            IfStmt *IfStatement = cast<IfStmt>(s);
            Stmt *Then = IfStatement->getThen();
            rewriter_->InsertText(Then->getBeginLoc(), "// visitor, the 'if' part\n", true, true);
            Stmt *Else = IfStatement->getElse();
            if (Else) {
              rewriter_->InsertText(Else->getBeginLoc(), "// visitor, the 'else' part\n", true, true);
            }
        }
      return true;
    }

private:
  ASTContext *context_;
  SourceManager *sm_;
  std::string caller_;
  Rewriter *rewriter_;
};


// Matcher 匹配模式
class IfStmtHandler : public MatchFinder::MatchCallback {
public:
  IfStmtHandler(Rewriter &Rewrite) : Rewrite(Rewrite) {}

  virtual void run(const MatchFinder::MatchResult &Result) {
    if (const IfStmt *IfS = Result.Nodes.getNodeAs<clang::IfStmt>("ifStmt")) {
      const Stmt *Then = IfS->getThen();
      Rewrite.InsertText(Then->getBeginLoc(), "// matcher, the 'if' part\n", true, true);

      if (const Stmt *Else = IfS->getElse()) {
        Rewrite.InsertText(Else->getBeginLoc(), "// matcher the 'else' part\n", true, true);
      }
    }
  }
private:
  Rewriter &Rewrite;
};

class ReWriteClassConsumer : public clang::ASTConsumer {
public:
  explicit ReWriteClassConsumer(ASTContext *context, SourceManager *sm,
                                  Rewriter *rw)
      : visitor_(context, sm, rw), sm_(sm), rewriter_(rw), handlerif_(*rw) {}

  ~ReWriteClassConsumer() {
    llvm::outs() << "I have finished rewrite." << "\n";

  }
  virtual void Initialize(ASTContext &Context) override {
    context_ = &Context;
    matcher_.addMatcher(ifStmt().bind("ifStmt"), &handlerif_);

  }


  virtual void HandleTranslationUnit(clang::ASTContext &context) override {

    if (VISITORDump) {
      // 使用遍历模式
      visitor_.TraverseDecl(context.getTranslationUnitDecl());
    } else {
      // 使用匹配模式
      matcher_.matchAST(context);
    }
  }

private:
  ReWriteClassVisitor visitor_;
  ASTContext *context_;
  SourceManager *sm_;
  Rewriter *rewriter_;
  IfStmtHandler handlerif_;
  MatchFinder matcher_;
};

class ReWriteClassAction : public clang::ASTFrontendAction {
public:
  void EndSourceFileAction() override {
    //rewriter_.getEditBuffer(rewriter_.getSourceMgr().getMainFileID()) .write(llvm::outs());
    rewriter_.overwriteChangedFiles();
  }


  virtual std::unique_ptr<clang::ASTConsumer>
  CreateASTConsumer(clang::CompilerInstance &compiler, llvm::StringRef in_file) {
    // 屏蔽错误信息输出
    compiler.getDiagnostics().setClient(new IgnoringDiagConsumer()); // 相当于-w
    rewriter_.setSourceMgr(compiler.getSourceManager(), compiler.getLangOpts());
    return std::make_unique<ReWriteClassConsumer>(
        &compiler.getASTContext(), &compiler.getSourceManager(), &rewriter_);
  }

private:
  Rewriter rewriter_;
  };


//命令行参数: ast.cpp --  屏蔽编译数据库找不到
int main(int argc, const char **argv) {
  llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);

  // Initialize targets for clang module support.
  llvm::InitializeAllTargets();
  llvm::InitializeAllTargetMCs();
  llvm::InitializeAllAsmPrinters();
  llvm::InitializeAllAsmParsers();

  auto ExpectedParser =
      CommonOptionsParser::create(argc, argv, ClangCheckCategory);
  if (!ExpectedParser) {
    llvm::errs() << ExpectedParser.takeError();
    return 1;
  }
  CommonOptionsParser &OptionsParser = ExpectedParser.get();
  ClangTool Tool(OptionsParser.getCompilations(),
                 OptionsParser.getSourcePathList());

  std::unique_ptr<FrontendActionFactory> FrontendFactory;

  FrontendFactory = newFrontendActionFactory<ReWriteClassAction>();

  return Tool.run(FrontendFactory.get());
}


验证

文件:ast.cpp

//for linux,__cdecl
// g++ -g test_objetc_member_function_linux.cpp -std=c++11 -I../src -o test_objetc_member_function_linux
#include<iostream>
#include "stub.h"
using namespace std;

template<typename T>
void swap(T &a, T &b){
    T temp = a;
    a = b;
    b = temp;
}

class A{
    int i;
public:
    int foo(int a){
        if(a > 1)
            cout<<"I am A_foo:"<< a <<endl;
        else
            cout<<"I am A_foo > 1:"<< a <<endl;
        return 0;
    }
    static int bar(int b){
        cout<<"I am A_bar:"<< b <<endl;
        return 0;        
        
    }
};

int foo_stub(void* obj, int a)
{   
    A* o= (A*)obj;
    cout<<"I am foo_stub"<<endl;
    return 0;
}

int main()
{
    Stub stub;
    stub.set(ADDR(A,foo), foo_stub);
    A a;
    a.foo(1);
    int aa = 1, bb=2;
    swap(aa, bb);
    cout<<aa<<bb<<endl;
    
    return 0;
}

执行:rewriter -visitor ast.cpp --

结果:



再执行:rewriter ast.cpp --

结果:

Tags:

最近发表
标签列表