最近找到国外一大神做的利用LLVM和Clang写的修改源代码的例子,恰好项目需要做一个source-to-source的编译器与此相关,所以部分翻译一下这位大神的文章。
原文地址: http://eli.thegreenplace.net/2014/05/01/modern-source-to-source-transformation-with-clang-and-libtooling/
在几年前Eli-Bendersky在博客上写过一篇关于如何利用Clang制作source-to-source编译器的文章(文章链接) ,在那篇旧的博文里他写了一小段如何利用Clang改写C++源码的完整代码,据说那篇文章非常火,但LLVM在这几年已经取得了很大的进步,所以Eli-Bendersky利用最新的LLVM与Clang重写了那些代码。
实例代码的效果如下:
输入源文件:
void foo(int* a, int *b) {
if (a[0] > 1) {
b[0] = 2;
}
}
输出源文件:
// Begin function foo returning void
void foo(int* a, int *b) {
if (a[0] > 1) // the 'if' part
{
b[0] = 2;
}
}
// End function foo
将这个例子的代码作为蓝本进行改造,就可以很快地做出属于自己的source-to-source编译器。
先说说代码的思路:
1.ASTConsumer负责读取Clang解析出来的AST树
2.在ASTConsumer中重写HandleTopLevelDecl函数用以检测源码中的函数声明语句(见上面效果代码)
3. RecursiveASTVisitor类负责实际对源码的改写
4. 在RecursiveASTVisitor中重写VisitStmt函数与VisitFunctionDecl函数实现源码中目标语素的检测以及改写动作
5. 改写好的源码送入Rewriter类中,进行写入源代码文件的动作
代码:
//------------------------------------------------------------------------------
// Tooling sample. Demonstrates:
//
// * How to write a simple source tool using libTooling.
// * How to use RecursiveASTVisitor to find interesting AST nodes.
// * How to use the Rewriter API to rewrite the source code.
//
// Eli Bendersky ([email protected])
// This code is in the public domain
//------------------------------------------------------------------------------
#include <sstream>
#include <string>
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/ASTConsumers.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
using namespace clang::driver;
using namespace clang::tooling;
static llvm::cl::OptionCategory ToolingSampleCategory("Tooling Sample");
// By implementing RecursiveASTVisitor, we can specify which AST nodes
// we're interested in by overriding relevant methods.
class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> {
public:
MyASTVisitor(Rewriter &R) : TheRewriter(R) {}
bool VisitStmt(Stmt *s) {
// Only care about If statements.
if (isa<IfStmt>(s)) {
IfStmt *IfStatement = cast<IfStmt>(s);
Stmt *Then = IfStatement->getThen();
TheRewriter.InsertText(Then->getLocStart(), "// the 'if' part\n", true,
true);
Stmt *Else = IfStatement->getElse();
if (Else)
TheRewriter.InsertText(Else->getLocStart(), "// 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->getLocEnd().getLocWithOffset(1);
TheRewriter.InsertText(ST, SSAfter.str(), true, true);
}
return true;
}
private:
Rewriter &TheRewriter;
};
// Implementation of the ASTConsumer interface for reading an AST produced
// by the Clang parser.
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 (DeclGroupRef::iterator b = DR.begin(), e = DR.end(); b != e; ++b) {
// Traverse the declaration using our AST visitor.
Visitor.TraverseDecl(*b);
(*b)->dump();
}
return true;
}
private:
MyASTVisitor Visitor;
};
// For each source file provided to the tool, a new FrontendAction is created.
class MyFrontendAction : public ASTFrontendAction {
public:
MyFrontendAction() {}
void EndSourceFileAction() override {
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());
}
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef file) override {
llvm::errs() << "** Creating AST consumer for: " << file << "\n";
TheRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts());
return llvm::make_unique<MyASTConsumer>(TheRewriter);
}
private:
Rewriter TheRewriter;
};
int main(int argc, const char **argv) {
CommonOptionsParser op(argc, argv, ToolingSampleCategory);
ClangTool Tool(op.getCompilations(), op.getSourcePathList());
// ClangTool::run accepts a FrontendActionFactory, which is then used to
// create new objects implementing the FrontendAction interface. Here we use
// the helper newFrontendActionFactory to create a default factory that will
// return a new MyFrontendAction object every time.
// To further customize this, we could create our own factory class.
return Tool.run(newFrontendActionFactory<MyFrontendAction>().get());
}
Eli-Bendersky在Github上有大量的LLVM示例代码:
https://github.com/eliben/llvm-clang-samples
上面的代码可在这里找到(可能随时更新):
https://github.com/eliben/llvm-clang-samples/blob/master/src_clang/tooling_sample.cpp
麻雀虽小五脏具全,对上述的代码进行适当的修改很快就可以写出符合自己需要的source-to-source编译器了。