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>

...

自定义规则分析

  1. 能不能写

    即便有AST支持,也不是所有规则都能写成代码,例如:

    方法命名要标示清楚当前的的动作时机,did/will 等

  2. 选扫描类型(源码字符串扫描、AST节点访问、AST匹配)

  3. 怎么写(思路)

编写思路

  1. 先写个简单的目标代码

  2. 使用clang dump生成AST,观察结构

  3. 确定入手的节点

  4. 使用关键字在规则模版中找到对应的回调方法(method、property、block、if等)

  5. 根据节点获取相关信息 (property名字、类型、可读写等)

  6. 根据规则写检测逻辑 (字符串搜索、正则匹配等)

  7. 不符合就加入扫描报告

自定义规则环境准备

官方的教程在这里: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文件,我们可以直接调试规则:

  1. scheme选中规则名IllegalPropertyAbbreviations

  2. 点击scheme名,选择Edit scheme

  3. 左侧菜单选择Run,右侧Executable选择OCLint源码目录的可执行文件:build/oclint-release/bin/oclint-exe (如果没有找到,请打开build/oclint-release/bin/目录,把oclint-21.03文件改名为oclint-exe

  4. 点击上方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规则目录,第二个参数是扫描源码文件

  5. 点击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工程可以直接生成规则动态库,但文件会很大,需要做如下修改:

  1. scheme选中规则名IllegalPropertyAbbreviations

  2. 点击scheme名,选择Edit scheme

  3. 修改Build Configuration为MiniSizeRel,点击Close

  4. 编译一次

  5. 在Xcode打开Product文件夹,找到 libIllegalPropertyAbbreviationsRule.dylib

    (目录:oclint-ruleproject/rules.dl/MinSizeRel)

  6. 把它放到生产版本的OCLint目录lib/oclint/rules/下即可生效

相关文档

OCLint超详细官方文档

Clang相关知识文档

可以使用chrome自带翻译阅读

标签:oclint, oclint custom rule, scan code