clang教程之实现源源变化

声明:本教程来自于Eli Bendersky's website

原文地址:http://eli.thegreenplace.net/2014/05/01/modern-source-to-source-transformation-with-clang-and-libtooling/

众所周知,LLVM是一个自由软件项目,它是一种编译器基础设施,以C++写成。其发端源于2000年伊利诺伊大学厄巴纳-香槟分校(UIUC)的维克拉姆·艾夫(Vikram Adve)与其第一个博士生克里斯·拉特纳(Chris Lattner)的研究,彼时他们想要为所有静态及动态语言创造出动态的编译技术。现在使用LLVM来作为中端(middle-end)优化和后端目标代码生成的人很多,开源中也有很多基于LLVM进行二次开发的工具,比如之前NVIDIA贡献的nvptx的code-generator和klee。而llvm的前端,在llvm3(具体版本忘记了)之前使用的是GCC,之后使用的是clang。clang这个前端提供了很多sema静态分析工具,可以说已经超出了一般的编译器前端的范畴。

clang的功能如此强大,但是却很少发现有人对这部分知识进行介绍。我这里选取了Eli Bendersky的博客进行翻译介绍,作者现在是Google TensorFlow组的工程师,中间添加了我自己的理解,如果有错误,望大家指出。

首先介绍一下效果,输入是这样的一段带if的代码

 void foo(int* a, int *b) {
if (a[] > ) {
b[] = ;
}
}

经过自己做的工具后,完成以下两部分的功能:

1. 识别if的true-body和false-body,并添加相应的注释

2. 识别函数入口和函数出口,添加注释

介绍一下主要的实现思路:

1. 创建ClangTool,也就是使用libTooling的方式,解析输入的参数,将第1个参数作为源码文件进行读取

2. 将源码送到ASTConsumer中,进行解析

3. ASTConsumer中,重载HandleTopLevelDecl识别所有的函数声明

4. 调用MyASTVisitor这个类(继承至TheWriter)中的VisitStmt函数,对所有的语句进行遍历,调用VisitFunctionDecl函数,对函数声明进行处理

5. 在遍历中识别是否是IfStmt,然后对ture-body和false-body进行识别,并添加注释

6. 将修改完的送回TheRewriter,进行写回

现在粘一下源码LoopConvert.cpp

//------------------------------------------------------------------------------
// 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 (eliben@gmail.com)
// 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/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.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();
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());
}

源码介绍到这里,现在说一下编译,这种项目编译起来比较麻烦……

我选择的环境是Ubuntu16.04+LLVM4.0+Clang4.0 的环境,我已经发过一个使用binary进行安装llvm教程,当然,那个教程不适用于这里,我改天会再发一个教程,如何使用源码进行编译

这里假设大家和我使用的是相同的环境,因为LLVM4.0到5.0经历了比较大的改动,4.0的代码在5.0上正常编译时非常正常的。

1. 在源码的clang/tools文件夹下(大概是~/llvm-src/llvm-4.0.0.src/tools/clang/tools/下),新建文件夹extra

在文件夹内新建CMakeLists.txt,写入

add_subdirectory(loop-convert)

这里是告诉cmake工具,下边还有一级目录,叫做loop-convert

2. 再在extra中新建loop-convert文件夹

3. 在loop-convert中新建CMakeLists.txt,写入

set(LLVM_LINK_COMPONENTS support)

add_clang_executable(loop-convert
LoopConvert.cpp
)
target_link_libraries(loop-convert
clangTooling
clangBasic
clangASTMatchers
)

大概意思是添加LLVM的支持,使用 LoopConvert.cpp来编译出一个叫loop-convert的程序,然后将loop-convert和 clangTooling clangBasic clangASTMatchers链接在一起,这几个都是clang的库

现在loop-convert文件夹中应该有CMakeLists.txt  LoopConvert.cpp两个文件

现在的目录结构如下

clang/tools  ->extra -> loop-convert         ->CMakeLists.txt

...          CMakeLists.txt          LoopConvert.cpp

4. 现在,重新使用cmake生成Makefile文件,make后就能得到loop-convert了

loop-convert在 where_you_build/bin/下边(我是~/llvm-src/build/bin)

现在进行测试

首先编辑一个带if的程序(不推荐包含头文件,因为AST打印的时候,会把头文件也打印出来,不方便查看)

我使用的test.cpp如下

void foo(int* a, int *b) {
if (a[] > )
{
b[] = ;
}
}

使用./loop-convert test.cpp -- 命令进行测试, --表示没有特别的参数

Clang教程之实现源源变化的更多相关文章

  1. Clang编译选项和Pass构建

    编译选项相关: 想要添加的选项,以我添加的-fdpu为例子 能通过clang --help得到的选项,整体需要一个解析文件(好像在LLVM项目中都是通过后缀名为xxx.td和xxx.def的文件来进行 ...

  2. 2013 duilib入门简明教程 -- 界面布局(9)

        上一个教程实现的标题栏代码中,并没有看到处理自适应窗口大小的代码,但是窗口大小变化后,按钮的位置会跟着变化,这是因为我们将按钮放到了HorizontalLayout.VerticalLayou ...

  3. duilib入门简明教程 -- 界面布局(9)

        上一个教程实现的标题栏代码中,并没有看到处理自适应窗口大小的代码,但是窗口大小变化后,按钮的位置会跟着变化,这是因为我们将按钮放到了HorizontalLayout.VerticalLayou ...

  4. duilib入门简明教程 -- 界面布局(9) (转)

    原文转自:http://www.cnblogs.com/Alberl/p/3343806.html     上一个教程实现的标题栏代码中,并没有看到处理自适应窗口大小的代码,但是窗口大小变化后,按钮的 ...

  5. duilib教程之duilib入门简明教程9.界面布局

    上一个教程实现的标题栏代码中,并没有看到处理自适应窗口大小的代码,但是窗口大小变化后,按钮的位置会跟着变化,这是因为我们将按钮放到了HorizontalLayout.VerticalLayout,这样 ...

  6. 【xcode5的使用】

    layout: post title: "WWDC 2013 Session笔记 - Xcode5和ObjC新特性" date: 2013-06-13 10:05 comments ...

  7. cocos2d-x 系列文章介绍

    学习 cocos2d-x 一年多,从3.0bata 到 现在的 3.6 ,从最早没什么教程到现在官网繁多的资料教程,  cocos2d-x  的变化实在是大.刚开始学习 cocos2d-x 是到处找资 ...

  8. 【转载】cocos2d-x2.2.3和android的平台环境

    这两天试图按照教程来学习写游戏移植到的横版过关Android在.在网上找了很多教程,但版本号变化.所使用的工具有细微的差别.所以,现在我们还没有准备好,阅读后,下面的文章.最后能够顺利您的手机上跑起来 ...

  9. Xcode5和ObjC新特性

    Welcome to Xcode 5 这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看这篇总览.本文仅作为个人记录使用,也欢迎在许可协议范围内转载或使用,但是还烦请保留原文链接,谢谢您的 ...

随机推荐

  1. 图片加载框架之ImageLoader

    Android开发中,多少会接触到异步加载图片,或者加载大量图片的问题,而加载图片我们常常会遇到许多的问题,比如说图片的错乱,OOM等问题,对于这些问题解决起来会比较吃力,比较著名的就是Univers ...

  2. Service-stack.redis 使用PooledRedisClientManager 速度慢的原因之一

    现在越来越多的开发者使用service-stack.redis 来进行redis的访问,但是获取redisclient的方式有多种方式,其中有一种从缓冲池获取client的方式很是得到大家的认可. L ...

  3. Ironic 的 Rescue 救援模式实现流程

    目录 文章目录 目录 救援模式 实现 UML 图 救援模式 以往只有虚拟机支持救援模式,裸机是不支持的.直到 Queen 版本 Ironic 实现了这个功能.救援模式下,用户可以完成修复.Troubl ...

  4. 程序员查问题还是要找stackoverflow

    今天定位了一个问题,其实也不是多复杂. 现场的数据是postgres dump出来的,想拿到本地服务器restore后定位问题. 本地restore后报错,报sequence as data_type ...

  5. UITableView的使用总结

    直接贴代码了,很好理解,注释很全,一看就懂...... // // ViewController.m // TableViewSectionTitleDemo // // Created by 思 彭 ...

  6. 【.NET】CS0016: 未能写入输出文件“c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\d29b5393\123c3a1c\App_Code.odl3w4o6.dll”--“拒绝访问。 ”

    IIS部署网站或者Webservice时,出现以下问题: CS0016: 未能写入输出文件“c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Tempor ...

  7. Leetcode之广度优先搜索(BFS)专题-127. 单词接龙(Word Ladder)

    Leetcode之广度优先搜索(BFS)专题-127. 单词接龙(Word Ladder) BFS入门详解:Leetcode之广度优先搜索(BFS)专题-429. N叉树的层序遍历(N-ary Tre ...

  8. 【Python开发】使用python中的matplotlib进行绘图分析数据

    matplotlib 是python最著名的绘图库,它提供了一整套和matlab相似的命令API,十分适合交互式地进行制图.而且也可以方便地将它作为绘图控件,嵌入GUI应用程序中. 它的文档相当完备, ...

  9. nginx文件路径配置(root|alias)

    nginx指定文件路径主要有两种方式:root|alias. 那么他们究竟有什么区别呢? 指令的使用方法和作用域: [root] 语法:root path 默认值:root html 配置段:http ...

  10. appium环境搭建(二)----搭建android开发环境

    一.安装Java环境: java 环境分JDK 和JRE ,JDK就是Java Development Kit.简单的说JDK是面向开发人员使用的SDK,它提供了Java的开发环境和运行环境. JRE ...