OCLint自定义规则
OCLint
Clang本身自带AST树遍历,可以实现静态代码检查。
OCLint是基于Clang tool 的静态代码分析工具,换句话说相当于做了一层针对Objective-C的封装。它的核心能力是对 Clang AST 进行分析,最后输出违反规则的代码信息,并且导出指定格式的报告。
使用OCLint自带规则扫描工程
下载oclint release包
安装必要工具
gem install xcpretty
官方下载
https://github.com/oclint/oclint/releases
这里使用21.03版本
安装文档:https://docs.oclint.org/en/stable/intro/installation.html
使用文档:https://docs.oclint.org/en/stable/intro/tutorial.html
扫描工程
为节省时间,这里以AFNetworking工程为扫描对象
git clone https://github.com/AFNetworking/AFNetworking.git
cd afnetworking
git checkout 3.2.1
编译工程
xcodebuild clean build -workspace AFNetworking.xcworkspace -scheme 'AFNetworking iOS' -sdk iphoneos -destination 'generic/platform=iOS' GCC_PRECOMPILE_PREFIX_HEADER=NO COMPILER_INDEX_STORE_ENABLE=NO | tee xcodebuild.log > /dev/null
用xcpretty格式化xcode日志
xcpretty -r json-compilation-database -o compile_commands.json < xcodebuild.log > /dev/null
oclint扫描
path-to-oclint-release/bin/oclint-json-compilation-database -- -report-type pmd -o oclintReport.xml -list-enabled-rules -rc LONG_LINE=9999 -max-priority-1=9999 -max-priority-2=9999 -max-priority-3=9999
提示:可以在项目目录下创建.oclint文件,填写更多参数:
https://docs.oclint.org/en/stable/howto/rcfile.html
扫描完成后将在当前目录创建oclintReport.xml文件,打开即可看到扫描出来的问题列表:
...
<file name="afnetworking/AFNetworking/AFURLRequestSerialization.m">
<violation begincolumn="1" endcolumn="1" beginline="707" endline="752" priority="3" rule="high ncss method" ruleset="size" >
Method of 42 non-commenting source statements exceeds limit of 40
</violation>
</file>
<file name="afnetworking/AFNetworking/AFURLRequestSerialization.m">
<violation begincolumn="5" endcolumn="5" beginline="1264" endline="1284" priority="5" rule="prefer early exits and continue" ruleset="convention" >
Use early exit/continue to simplify code and reduce indentation
</violation>
</file>
...
自定义规则分析
能不能写
即便有AST支持,也不是所有规则都能写成代码,例如:
方法命名要标示清楚当前的的动作时机,did/will 等
选扫描类型(源码字符串扫描、AST节点访问、AST匹配)
怎么写(思路)
编写思路
先写个简单的目标代码
使用clang dump生成AST,观察结构
确定入手的节点
使用关键字在规则模版中找到对应的回调方法(method、property、block、if等)
根据节点获取相关信息 (property名字、类型、可读写等)
根据规则写检测逻辑 (字符串搜索、正则匹配等)
不符合就加入扫描报告
自定义规则环境准备
官方的教程在这里:http://docs.oclint.org/en/stable/index.html#development
先安装依赖
brew install cmake lcov openssl ninjia
拉OCLint源码仓库
git clone devtool/ios_oclint_dev_project
进入OCLint根目录的oclint-scripts文件夹,执行命令:
./make
此过程要点时间
返回OCLint项目根目录,创建oclint-xcodeproject文件夹,进入该文件夹,创建gen.sh脚本:
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
执行这个脚本:
bash gen.sh
顺利的话你会console输出看到:
-- Build files have been written to: /[your path]/oclint-xcodeproject
打开project文件,可以看到自带规则源码
三个抽象类
OCLint 提供了三个抽象类,以便我们来编写自定义规则。
AbstractSourceCodeReaderRule(源代码读取器规则)
提供了一种
eachLine
方法。我们可以获取每行的文本和当前行号。然后,我们可以处理文本。例如,我们可以计算文本的长度,可以理解它是否为注释,可以确定是否存在空格和制表符的混合使用,等等。AbstractASTVisitorRule(AST 访问者规则)
该抽象类提供了一系列节点被访问的接口,只需要重载某些方法,即可处理相应节点内的校验逻辑。
AbstractASTMatcherRule(AST 匹配器规则)
基于匹配模式,你需要构造一些匹配器并加载。只要找到匹配项,callback 就以该 AST节点作为参数调用method,你就可以在callback中收集违例信息。
这里简单就说这么多,我们只需要知道 OCLint 提供了抽象类,用于实现自定义规则。
使用scaffoldRule 脚本创建规则模版
这是由 OCLint 提供的一个脚手架。
使用脚手架创建规则可以使用该脚本可以方便的创建自定义规则。
文档:http://docs.oclint.org/en/stable/devel/scaffolding.html#creating-rules-with-scaffolding
本教程实现最具代表性的AbstractASTVisitorRule抽象类,实现一个简单的自定义规则:
property名称包含不规范缩写
终端cd到自定义规则工程根目录,执行:
oclint-scripts/scaffoldRule IllegalPropertyAbbreviations -t ASTVisitor -c custom
这里自定义规则名叫IllegalPropertyAbbreviations,类型是ASTVisitor
其他类型可以通过执行 oclint-scripts/scaffoldRule --help 查看
更新rule project
cd oclint-ruleproject/
bash gen.sh
xcode可以看到左侧多了刚刚创建的代码文件
写个例子
在oclint-ruleproject目录下创建debug.m文件:
touch debug.m
open debug.m
在debug.m文件里编写需要扫描的代码:
#import <Foundation/Foundation.h>
@interface DDDebugObject : NSObject
@property (nonatomic, copy) NSString *resultStr; //这是需要检测的变量名
@end
@implementation DDDebugObject
@end
使用clang dump查看例子代码的AST
执行:
$ clang -Xclang -ast-dump -fsyntax-only debug.m
...
|
|-ObjCInterfaceDecl DDDebugObject //interface声明
| |-super ObjCInterface 'NSObject' //继承NSObject
| |-ObjCImplementation 'DDDebugObject'
| |
| |-ObjCPropertyDecl titleStr 'NSString *' readwrite copy nonatomic //property声明
| |
| |-ObjCMethodDecl implicit - titleStr 'NSString *' //titleStr的getter方法
| `-ObjCMethodDecl implicit - setTitleStr: 'void' //titleStr的setter方法
| `-ParmVarDecl titleStr 'NSString *' //setter方法入参
|
|
`-ObjCImplementationDecl DDDebugObject //implement声明
|-ObjCInterface 'DDDebugObject'
|
|-ObjCIvarDecl implicit _titleStr 'NSString *' synthesize private //自动合成ivar
|
|-ObjCPropertyImplDecl <invalid sloc> titleStr synthesize //property实现
| |-ObjCProperty 'titleStr'
| `-ObjCIvar '_titleStr' 'NSString *'
|
|-ObjCMethodDecl implicit - titleStr 'NSString *' //getter方法实现
`-ObjCMethodDecl implicit - setTitleStr: 'void' //setter方法实现
`-ParmVarDecl titleStr 'NSString *' //setter方法入参
找到需要校验的节点
能取到property名字的节点都好几个,这里随便选一个:
|-ObjCPropertyImplDecl <invalid sloc> titleStr synthesize //titleStr对应的ivar
| |-ObjCProperty 'titleStr'
| `-ObjCIvar '_titleStr' 'NSString *'
需要关注的节点是:
ObjCPropertyImplDecl
只要取出这个节点里面的属性名,就可以用来检测了
写代码
回到我们创建好的IllegalPropertyAbbreviations.cpp
文件,可以发现有一千多行的回调方法。
认真观察下,可以发现有很多Objc相关的方法。
1. 使用关键字在规则模版中找到对应的调用方法
这里我们直接在文件搜索关键字:ObjCPropertyImplDecl
, 找到了这个回调:
/* Visit ObjCPropertyImplDecl
bool VisitObjCPropertyImplDecl(ObjCPropertyImplDecl *node)
{
return true;
}
*/
打开这个回调的注释,我们在这里写检测代码。
2. 获取节点名等相关信息
查询oclint和clang文档之后,可以找到获取属性名的方法:
//获取节点名等相关信息
ObjCPropertyDecl *decl = node->getPropertyDecl();
string name = decl->getNameAsString();
3. 根据规则检查
为了实现检测规则,简单定义一下缩写字数组, 然后判断是否包含这些关键字
vector<string> abbreviates {
"btn",
"str",
"lb",
"txt",
"img",
"vc"
};
4. 如果违规就加入violationSet
完整代码如下:
/* Visit ObjCPropertyImplDecl
*/
bool VisitObjCPropertyImplDecl(ObjCPropertyImplDecl *node)
{
//获取节点名等相关信息
ObjCPropertyDecl *decl = node->getPropertyDecl();
//titleStr
string name = decl->getNameAsString();
//根据规则检查是否合规
vector<string> abbreviates {
"btn",
"str",
"lb",
"txt",
"img",
"vc"
};
transform(name.begin(),name.end(),name.begin(),::tolower);
for (auto i = abbreviates.begin(); i != abbreviates.end(); i++) {
string eachAbbreviate = *i;
//如果违规就加入violationSet
if (name.find(eachAbbreviate) != string::npos) {
string rawName = decl->getNameAsString();
addViolation(decl, this, "Property "+rawName+" using illegal abbreviate:" +
eachAbbreviate);
}
}
return true;
}
5. 调试规则
使用之前写好的debug.m文件,我们可以直接调试规则:
scheme选中规则名
IllegalPropertyAbbreviations
点击scheme名,选择Edit scheme
左侧菜单选择Run,右侧Executable选择OCLint源码目录的可执行文件:
build/oclint-release/bin/oclint-exe
(如果没有找到,请打开build/oclint-release/bin/
目录,把oclint-21.03
文件改名为oclint-exe
)点击上方Arguments,在Arguments Passed On Launch下点击➕号,输入:
-R=/OCLint源码仓库绝对路径/oclint-ruleproject/rules.dl/Debug /OCLint源码仓库绝对路径/oclint-ruleproject/debug.m -- -x objective-c -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
第一个参数是debug规则目录,第二个参数是扫描源码文件
点击Close,点击运行,可以看到Xcode终端输出:
OCLint Report
Summary: TotalFiles=1 FilesWithViolations=1 P1=0 P2=0 P3=1
.../oclint-base/oclint-ruleproject/debug.m:6:1: illegal property abbreviations [custom|P3] Property titleStr using illegal abbreviate:str
[OCLint (https://oclint.org) v21.03]
Program ended with exit code: 0
编译生成dylib动态库规则文件
调试完规则之后,使用当前的xcode工程可以直接生成规则动态库,但文件会很大,需要做如下修改:
scheme选中规则名
IllegalPropertyAbbreviations
点击scheme名,选择Edit scheme
修改Build Configuration为MiniSizeRel,点击Close
编译一次
在Xcode打开Product文件夹,找到 libIllegalPropertyAbbreviationsRule.dylib
(目录:oclint-ruleproject/rules.dl/MinSizeRel)
把它放到生产版本的OCLint目录
lib/oclint/rules/
下即可生效
相关文档
可以使用chrome自带翻译阅读