OCLint 自定义规则101
[TOC]
一 、准备开发环境
mac系统安装有Xcode,git,ruby(一般都会有),还有要会科学上网
在OCLint的github上clone下代码 https://github.com/oclint/oclint 。(不要下载官方网站的代码),目录如下:
1
2
3
4
5
6
7├── README.md
├── oclint-core
├── oclint-driver
├── oclint-metrics
├── oclint-reporters
├── oclint-rules
└── oclint-scriptscd
进入oclint-scripts
文件加,执行./make
。大约30分钟后编译完成,大概过程是下载LLVM、clang的源代码,编译LLVM、clang与OCLint预计OCLint的默认规则。(如果通过官网下载的OCLint代码,执行make时会通过SVN下载LLVM的代码,然而即便我科学上网了还是总是下载不成功,建议直接同git上下载OCLint的源码)编译成功后就可以写规则并且编译执行了。为了方便,OCLint提供了一个叫
scaffoldRule
的脚本程序,它在oclint-rules
目录下。我们通过他传入要生成的规则名,级别,类型,脚本就会在目录oclint-rules/rules/custom/
自动帮我们生成一个模板代码,并且加入编译路径中。举个例子:1
2生成一个名为HdwTestRule类型是ASTVisitor的规则模板
oclint-scripts/scaffoldRule HdwTestRule -t ASTVisitor生成两个文件:
1
2
3
4CMakeLists.txt 是对规则HdwTestRule的编译描述,由make程序在编译时使用。HdwTestRule.cpp的内容之后再分析。
├── custom
│ ├── CMakeLists.txt
│ └── HdwTestRule.cpp接着就可以对新添加的内容进行编译了,不过相比于重新执行
oclint-scripts/make
来说有一个更加优雅的办法,就是将规则相关的内容整合成一个Xcode工程,并且我们的每个规则都是一个scheme,编译时可以只选择编译那个选择的规则生成对应的dylib。做饭很简单,OCLint工程使用CMakeLists
的方式维护各个文件的依赖关系,我们可以使用CMake自带的功能将这些CMakeLists生成一个xcodeproj
工程文件。but how?下面栗子:1
2
3
4
5在OCLint源码目录下建立一个文件夹,我这里命名为oclint-xcoderules
mkdir oclint-xcoderules
cd oclint-xcoderules
创建一个脚本(代码如下段),并执行它(我写的方便修改参数,其实里面就一句命令,直接执行也行)(PS:刚创建的文件是没有执行权限的,不要忘了chmod)
./create-xcode-rules.sh1
2! /bin/sh -e
cmake -G Xcode -D CMAKE_CXX_COMPILER=../build/llvm-install/bin/clang++ -D CMAKE_C_COMPILER=../build/llvm-install/bin/clang -D OCLINT_BUILD_DIR=../build/oclint-core -D OCLINT_SOURCE_DIR=../oclint-core -D OCLINT_METRICS_SOURCE_DIR=../oclint-metrics -D OCLINT_METRICS_BUILD_DIR=../build/oclint-metrics -D LLVM_ROOT=../build/llvm-install/ ../oclint-rules于是我们就得到了xcode工程:
1
2
3
4
5
6
7
8
9
10├── CMakeCache.txt
├── CMakeFiles
├── CMakeScripts
├── OCLINT_RULES.build
├── OCLINT_RULES.xcodeproj
├── cmake_install.cmake
├── create-xcode-rules.sh
├── lib
├── rules
└── rules.dl打开
OCLINT_RULES.xcodeproj
:选择自己的规则,编译,成功后就可以在Products下看到生成的dylib啦,想想还有点小激动呢
二、开发规则
上面准备环境的一些步骤其实都是来自对OCLint官网的总结,同样如何去写自定义的规则还是要去看文档 http://docs.oclint.org/en/stable/ ,甚至于还要看clang的文档 http://clang.llvm.org/docs/IntroductionToTheClangAST.html 。我这里只能给出一些小建议,大概就是如何123去完成自定义的规则,希望再你阅读完文档但是不知道如何下手的时候能够帮到忙。
《 寓言故事》
小明看了OCLint的官方文档,了解了一下clang AST的知识,并读了一些OCLint AST的源码,大概知道OCLint调用clang 的API把一个个源文件生成一个一个AST,然后遍历树中的每个节点传入各个规则的整个过程。然后小明按照与上面类似的步骤最终生成并打开了OCLINT_RULES.xcodeproj。他觉得最好的学习方式是先阅读OCLint给出的默认规则的代码,大概读了几个后他似有所悟,于是就打开了之前自定义自动生成的HdwTestRule.cpp
文件开始自己编写。
小明决定写一个规则用于检查代码中“含有self的block中没有使用strongify”的那些block。
小明打开HdwTestRule.cpp,他看到一大堆(大概有两千行)注释代码,类似于:
1 | class MissingStrongifyInCatchSelfBlockRule : public AbstractASTVisitorRule<MissingStrongifyInCatchSelfBlockRule> |
小明嘴角微微一笑,因为他知道,就在上午他刚读过的OCLint的源码中表明这些以Visit开头的百十个函数都是OCLint提供给开发者的回调函数。小明机智的按下了command + f
输入了block
,结果他找到了两个回调:
1 | bool VisitBlockExpr(BlockExpr *node) |
小明知道BlockExpr
和BlockDecl
都是clang定义的AST树节点的类型,OCLint在调用clang API遍历语法树时遇到block表达式或声明语句会分别调用BlockExpr
和BlockDecl
对应的两个visit回调。但是小明现在有点尴尬,因为它不确定应该在VisitBlockExpr中处理还是在VisitBlockDecl处理检测含有self的block中没有使用strongify
这件事情,并且他也忘了上午刚刚在clang文档中看到的block节点内部的遍历方法。小明有点慌乱,在浏览器中不停的切换着OCLint文档页面与clang文档页面的tab,他自己也不知道自己要找什么,突然,不知道为什么,也许是隔壁小王的一个喷嚏提醒了他,clang有一个dump功能可以查看一段代码的语法树结构,小明很激动,他觉得这也许能帮助他。于是他飞快的建立了一个名为testOCLint.xcodeproj
的xcode控制台应用工程,并写了三行代码:
1 | int main(int argc, const char * argv[]) { |
不到30秒做完这一切的他似乎很是得意,右手拿起刚刚泡好的热茶喝了一口,可是突然,不知是茶有点热还是他忘记了clang如何dump出AST,嘴角刚漏出的笑意僵住了,只见他飞速的移动鼠标在浏览器上搜索了一会,随即又在控制台输入:
1 | clang -Xclang -ast-dump -fsyntax-only ./testOCLint/main.m |
看着命令行飞速闪过的彩色的输出,小明忘记了刚刚的一点不快,眼神最终钉在了最后几行输出上:
1 | `-FunctionDecl 0x7fe97956cc70 <./testOCLint/main.m:11:1, line:16:1> line:11:5 main 'int (int, const char **)' |
小明知道这是main函数对应的AST结构,他很快看明白这些内容的大概,确定了自己应该处理BlockDecl回调,可他还不太确定应该如何遍历这个节点内容来做到检测含有self的block中没有使用strongify
,但他心中隐隐有个想法,于是改定了一下main函数:
1 | @interface TestClass : NSObject |
紧接着再次dump:
1 | |-ObjCInterfaceDecl 0x7fd807eb4298 prev 0x7fd807e89570 <line:193:1, line:197:2> line:193:12 NSXPCListenerEndpoint |
这次命令行滚动的输出已经不能对他带来任何波澜,他的目光紧紧盯着那关键的几行:
1 | `-BlockDecl 0x7fd807eb4838 <line:19:17, line:21:5> line:19:17 |
看到BlockDecl下面的capture节点,他笑了,他之前就知道如果在visitBlockDecl中找到含有self capture节点,在判断其有无strongify,问题就解决了。至于如何得到capture节点确实难不了他,只见他淡定的在Google搜索框里输入clang BlockDecl
,又毫不犹豫的几乎看都不看搜索列表就点开了第一条,因为他知道第一条总是链接到clang的官方文档。只见浏览器打开的新的tab页果然是clang官方文档并且惊人的就是介绍BlockDecl类的相关内容的页面。小明只定睛一看,随即眼睛又微微闭上,好似在说“我就知道!”。接着他又熟练的按下的command + f
输入capture
。几次回车后,他找到了几个相关函数:
1 | ArrayRef< Capture > captures () const |
这时他明白了如何获得bock中捕获变量,随即就写了如下代码:
1 | class MissingStrongifyInCatchSelfBlockRule : public AbstractASTVisitorRule<MissingStrongifyInCatchSelfBlockRule> |
现在就剩下函数BlockDeclHasStrongify
如何实现的问题,判断这个block中是否含有@strongify
,他觉得只要得到block部分对应的源码,然后再字符串匹配一下strongify就行。关于如何获得AST节点在源码中的位置,以及获得对应的代码,他在之前阅读OCLint中预设规则源码的时候已经学到了,至于匹配查找他知道正则表达式可以使用。之后经历查文档->看dump->生成dylib->复制dylib到OCLint规则目录下->执行检测->查看报告->发现问题->修改代码重新生成dylib这个恶心又麻烦的调试过程后,终于完善了VisitBlockDecl
和BlockDeclHasStrongify
函数:
1 | bool BlockDeclHasStrongify(BlockDecl *node) |
就在这时小王叫他吃饭,小明对小王说他成功写了一个OC语法检查的插件,小王也说了自己今天的收获。然而这些都不影响他们晚饭照常点了黄焖鸡,并讨论国家大事……