长话短说!
这篇新手教程会让你弄清楚什么是clang、clang AST、clang plugins 和clang tools 等等,让你大概知道clang 可以解决什么问题,而且小白也是可以用clang libraries 来开发工具的 :)
01 Clang
是什么Clang 是一个以LLVM为后端的编译前端。编译前端主要负责parse 源码、检查错误,并生成抽象语法树 Abstract Syntax Tree (AST)。相较于其他编译器生成的AST,clang 生成的AST 更加接近C++ 源码,所以我们可以更加准确地在源码中进行查找和定位。并且,clang 还提供了丰富的库和API,让我们能在AST 上很方便地做遍历,搜索和修改等操作。我们在vscode 上用的代码自动补全工具clangd(或vim 的YouCompleteMe)就是用clang 来实现的。
(点击查看大图)Ref:https://jonasdevlieghere.com/...
02 什么时候会用到Clang
‣ 需要基于编译器的AST 对源码做精确的编辑
‣ 自动纠正不符合coding style 的源码
‣ 需要引入自定义的编译错误和警告
‣ 禁止用裸指针创建共享指针
‣ 声明了变量但是没有使用
‣ 基于C/C++ 源码的代码生成(code generation)
‣ 自动生成数据结构的序列化方法,反射方法
03Clang AST 是什么
‣ Clang AST 的节点是由几种没有共同基类的类来组成(建模)的
Clang’s AST nodes are modeled on a class hierarchy that does not have a common ancestor. -- 《Introduction to the Clang AST》(https://clang.llvm.org/docs/I...)
‣ 其中比较常用的四种class 是 Type , Decl , DeclContext , Stmt
‣ 每种节点都有专门的遍历函数来获取子节点
‣ ASTContext 里可以获取AST 的额外信息,比如源码的地址
‣ 例子:下面是一段简单的c++ 源码,我们可以看一下这段源码对应的AST
(点击查看大图)
运行结果:先忽略掉上面的(一堆看不懂而且很吓人的东西 TranslationUnitDecl ) ,我们可以看到我们定义的两个函数在AST 的节点类型是 FunctionDecl (也就是 Decl 的子类)。另外, CompoundStmt 是函数体的节点类型。其他的节点类型如果我们对照着源码来看的话,其实还是很好看懂的,比如 VarDecl 是变量声明, IntegerLiteral 是整数文字。
(点击查看大图)Ref:https://clang.llvm.org/docs/I...
04 Clang APIs
‣ Clang 提供C 和C++ API
‣ C API (libClang) 稳定但是不完整
‣ C++ API(LibTooling)完整但是不稳定‣ LibTooling 主要用于开发standalone 工具,并且plugins 只能使用LibTooling 开发下面的教程都是关于C++ API —— LibTooling 的Ref:https://clang.llvm.org/docs/T...
什么是Clang plugins 和Clang tools
用LibTooling 可以开发两种形式的clang 工具:plugins (clang 插件) 或standalone tools (不依赖clang 运行的独立工具)。
Clang plugins 会在编译时对AST 进行一些额外的检查或操作。plugin是动态库(.so),在运行时由编译器(也就是clang)加载,所以可以很容易地集成到编译环境中。
Clang tools 是独立的(standalone) 可执行程序,比如clang-check,clang-format,clang-tidy 这些llvm project 官方提供的standalone tools。
有时候两种形式的工具都能解决问题。例如下面是一个叫LAComment 的工具,提供了两种工具形式:
(点击查看大图)
这时候我们可以思考下面的问题,来决定是开发plugin 还standlone 工具:
‣ 能不能选择用哪个编译器?clang / gcc?
‣ 需不需要make 或者break build
‣ Clang 6.0 写的plugin Clang 7.0 不一定能用(API 不稳定!)
‣ 部署环境(如CI pipeline)使用哪种工具用户接入成本更低?
RecursiveASTVisitor v.s. ASTMatcher
上面提到的LibTooling 库有两种API framework: RecursiveASTVisitor 和ASTMatcher:‣ RecursiveASTVisitor 为大多数的AST 节点提供访问节点的hook bool VisitNodeType(NodeType *) ;
‣ ASTMatcher 是一套用来匹配和遍历AST 的DSL;‣ 两种framework 的选择并不互斥,必要时可以一起使用来解决比较复杂的问题!例子:https://github.com/banach-spa...
‣ 建议优先考虑用ASTMatcher,因为可以用clang-query 来验证DSL 是否正确。
感兴趣的同学可以尝试运行下面的命令,用clang-query 查看test.cc 里的函数节点。
(点击查看大图)
Ref:https://clang.llvm.org/docs/L...
05 如何写一个Clang工具
1. 想清楚目的。这应该是最值得花时间,也有可能是最难的一步。因为当我们剖析清楚目的之后,我们可能会发现我们要做的事情其实可以用比clang 更简单的工具来完成。而且,如果我们一定要用clang,只有当目的足够清晰,我们才能知道要在AST 上查找哪种类型的节点,并对其进行操作;
- 写一些最小的测试用例源码并用 clang -Xclang -ast-dump 来观察生成的AST;
- 找到想要匹配的AST 节点;
- 使用clang-query 来在测试用例上检验ASTMatcher;
- 将ASTMatcher 的DSL集成到clang LibTooling 的脚手架里。
06怎么创建Clang 工具项目
Llvm 的官方教程是用in tree的方法来创建工具的(即在 clang-llvm 的目录内加入工具代码) 。In tree 虽然setup 会比较简单,但是不便于做代码管理。https://clang.llvm.org/docs/L...
所以如果我想要有更好的版本管理,甚至是接入CI/CD,就应该用Out of tree 的方式来创建工具(即在 clang-llvm 目录外创建工具代码)。Out of tree 创建方式下面的开源教程非常详细,感兴趣的同学可以参考一下:
https://github.com/banach-spa...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。