最近在学习自定义规则的时候了解了一下Clang和一些编译知识,就写了一下总结。
Clang和LLVM
不论是OC还是Swift,都是Clang作为编译器前端,LLVM(Low Level Virtual Machine)作为编译器后端。
编译器前端
编译器前端会对代码进行预处理,进行词法分析(Lexical analysis),语法分析(Parsing),生成中间码(生成AST)然后根据CodeGen生成LLVM IR。 编译器后端会把前端输出的LLVM IR进行优化,然后生成机器码,最终生成Mach-O。 OCLint就是依赖于AST进行规制检查的。举个栗子:
31 int main(int i) {
32 if (i<0) {
33 return -i;
34 }
35 return i;
36 }
复制代码
在终端运行clang -Xclang -ast-dump -fsyntax-only $PATH
会log出一大堆东西,我们截取了最后一段
`-FunctionDecl 0x7f916b5407f8 <PATH.m:31:1, line:36:1> line:31:5 main 'int (int)'
|-ParmVarDecl 0x7f916b540770 <col:10, col:14> col:14 used i 'int'
`-CompoundStmt 0x7f916b540a50 <col:17, line:36:1>
|-IfStmt 0x7f916b5409c8 <line:32:5, line:34:5>
| |-<<<NULL>>>
| |-BinaryOperator 0x7f916b540908 <line:32:9, col:11> 'int' '<'
| | |-ImplicitCastExpr 0x7f916b5408f0 <col:9> 'int' <LValueToRValue>
| | | `-DeclRefExpr 0x7f916b5408a8 <col:9> 'int' lvalue ParmVar 0x7f916b540770 'i' 'int'
| | `-IntegerLiteral 0x7f916b5408d0 <col:11> 'int' 0
| |-CompoundStmt 0x7f916b5409a8 <col:14, line:34:5>
| | `-ReturnStmt 0x7f916b540990 <line:33:9, col:17>
| | `-UnaryOperator 0x7f916b540970 <col:16, col:17> 'int' prefix '-'
| | `-ImplicitCastExpr 0x7f916b540958 <col:17> 'int' <LValueToRValue>
| | `-DeclRefExpr 0x7f916b540930 <col:17> 'int' lvalue ParmVar 0x7f916b540770 'i' 'int'
| `-<<<NULL>>>
`-ReturnStmt 0x7f916b540a38 <line:35:5, col:12>
`-ImplicitCastExpr 0x7f916b540a20 <col:12> 'int' <LValueToRValue>
`-DeclRefExpr 0x7f916b5409f8 <col:12> 'int' lvalue ParmVar 0x7f916b540770 'i' 'int'
复制代码
以上这一段就是Clang对于main函数解析成的AST。里面有两种类型节点。一种是Stmt(Statement),另外一种是Decl(Declaration)。Stmt很明显,就是表达式的节点,具有操作性,比如CompoundStmt对应的{},ifstmt对应if,以上所有的Operator、Expr、Literal结尾的都是stmt的子类。Decl就对应的var,method,function。 我们可以通过某个节点往下遍历所有节点。比如FucntionDecl
往下遍历可以得到ParamVarDecl
和CompoundStmt
,然后通过CompoundStmt
可以得到IfStmt
和ReturnStmt
.
通过AST我们可以做什么?
1.我们可以通过这些节点做代码扫描。 2.我们可以接管这个过程,然后加入自定义代码。因为AST是属于编译器前端功能,还没有最后翻译成汇编,所以就有改动的余地。具体可以参考阳神的DynamicCocoa,它就是接管了AST,然后将AST利用自己写的Clang插件转换成JS文件。