背景
工程代码质量,一个永恒的话题。随着业务开发迭代速度越来越快,完全依赖人工保证工程质量也变得越来越不牢靠。所以,静态分析,这种可以帮助我们在编写代码的阶段就能及时发现代码错误,从而在根儿上保证工程质量的技术,就成为了 iOS 开发者最常用到的一种代码调试技术。Xcode 自带的静态分析工具 Analyze,通过静态语法分析能够找出在代码层面就能发现的内存泄露问题,还可以通过上下文分析出是否存在变量无用等问题。但是,Analyze 的功能还是有限,还是无法帮助我们在编写代码的阶段发现更多的问题。所以,这才诞生出了功能更全、定制化高、效率高的第三方静态检查工具。比如,OCLint、Infer、Clang 静态分析器等。 目前项目是以Objective-C语言编写,使用OCLint工具进行静态检查十分合适。本文主要讲解OCLint在工程中的应用,我会从如下几方面阐述从0到1实现iOS端代码静态检测的实践。
一,OCLint简介
一,什么是OCLint?
OCLint 是基于Clang的前端编译的,把我们的源码编译为了抽象语法树AST和llvm的字节码,并对其进行分析。它可应用于 C,C++,Objective-C 等语言,目的是提高软件质量并且减少代码中存在的潜在问题,是侦测编译器不可见的潜在缺陷的关键技术。 OCLint 旨于分析以下潜在问题:
- 可能出现的 bug:if/else/try/catch 等条件语句空的声明。
- 未使用的代码: 未使用的局部变量以及参数。
- 复杂的代码逻辑:高循环复杂度、NP 复杂度(懵)、 高 NCSS(懵)。
- 冗余代码:冗余的条件表达式以及无效的括号。
- 代码嗅觉:方法代码行过长或者参数过多。
- 不好的代码习惯:颠倒的逻辑和参数的错误分配。
- 自定义的规范
OCLint 具有以下先进的代码检验特性:
- 依靠源码的抽象语法树来提高分析的精确度以及效率,误报率低。
- 动态规则。
- 灵活可扩展的配置,确保用户可以自定义分析行为。
- 命令行式的调用使持续集成成为可能。
二,安装与使用:
三种安装方式:
1,HomeBrew安装 安装简单快捷,缺点是目前只支持安装0.13版本,无法安装最新0.15版本, 且不支持自定义规则。
2,安装包安装 进入 OCLint 在 Github 中的地址,选择 Release。选择最新版本的安装即可,缺点不支持自定义规则。
3,源码编译安装 和第二种安装方式一样,先下载最新安装包,不同之处在于需要执行./make脚本命令来下载自定义规则所用相关的配置和工具。
三,OCLint 工具的组成:
- oclint oclint 是 OCLint 工具集最主要的指令,主要作用是规则加载、编译分析选项以及生成分析报告
- oclint-xcodebuild 用于将 xcodebuild 生成的 log 文件 xcodebuild.log 转换为 JSON Compilation Database format 类型,推荐直接使用xcpretty,直接将编译结果输出为 compile_commands.json。
- oclint-json-compilation-databases作用是在 JSON Compilation Database format类型的编译文件 compile_commands.json 中提取必要的信息。
可以使用时序图来概括我们使用这几个指令的场景:
四,主要命令:
1.xcodebuild 命令编译工程
1 | xcodebuild -workspace OCLintDemo.xcworkspace \ |
2.oclint-json-compilation-database 指令来解析 compile_commands.json:
1 | oclint-json-compilation-database -e Pods -- -o=report.html \ |
二,如何自定义检测规则
前言:
整个自定义规则是使用c++编写,官方提供了对应接口及脚手架供我们使用,要实现自定义规则,必须实现OCLint提供的RuleBase类或其派生的抽象类。不同的规则有专用不同的抽象级别。
- 通用规则
编写通用规则,我们需要实现RuleBase接口。OCLint已经实现了很多通用规则,而且派生的抽象类都继承RuleBase,直接操作抽象类更加灵活方便。因此建议直接操作以下的抽象类来实现自定义规则。 - 源代码读取器规则
AbstractSourceCodeReaderRule提供了一种eachLine方法。我们可以获取每行的文本和当前行号。然后,我们可以处理文本。例如,我们可以计算文本的长度,可以理解它是否为注释,可以确定是否存在空格和制表符的混合使用,等等。 - AST访问者规则
AbstractASTVisitorRule遵循访客模式。在规则中,我们仅针对感兴趣的节点类型编写方法。OCLint提供了很多对应的visit方法,这些visit方法的返回布尔值用于控制遍历。AST访问者在访问当前节点时返回true时将继续其子节点或同级节点,反之亦然,当前visit方法返回false时它将停止。就是一个递归查询的操作。 - AST匹配器规则
AST匹配器规则都从AbstractASTMatcherRule类继承。我们需要添加合适的匹配器,找到匹配项后以当前AST节点为参数回调给我们。
准备工作:
首先我们要知道自定义规则是使用C++来写的,并最终以动态连结库来存储。要编写自定义规则还需要了解了一下clang AST的知识。
本质是OCLint调用clang 的API把一个个源文件生成一个一个AST,然后遍历树中的每个节点传入各个规则的一个过程。
我们可以使用如下命令查看某个文件的 AST 结构:
1 | clang -Xclang -ast-dump -fsyntax-only ./testOCLint/XXXTest.m |
解析后的AST终端输出:
获取到AST语法树后,需要根据目标找到关键节点进行自定义规则编码。刚开始我们可以参考官方已有的源码或OCLint 自定义规则。
生成源码文件:
进入如下目录找到脚手架工程:
我们通过他传入要生成的规则名,级别,类型,脚本就会在目录oclint-rules/rules/custom/自动帮我们生成一个模板代码,并且加入编译路径中,执行脚本:
1 | ./scaffoldRule xxxRule -t ASTVisitor |
这里我们自定义的规则继承自ASTVisitor,他会帮我们自动生成两个文件:
1 | #CMakeLists.txt 是对规则XXTestRule的编译描述,由make程序在编译时使用。xxxRule.cpp就是我们要写自定义规则代码的地方 |
一个简单的自定义规则代码如下:
前面已经讲到OCLint的Rule都是以动态库的形式存储的,官方提供了,脚手架工程及命令工具方便我们生成动态库。也提供了测试机制。由于篇幅原因且接入较复杂且坑很多,这里就不再详细阐述。
在我们生成了动态库后,只需把动态库拷贝到OCLint的Rule文件下即可生效。自己生成的规则,同样支持-disable-rule 来禁止规则生效。
三,增量检测方案
由于OCLint本身不支持增量检查。为了实现静态检测接入CI发版流程。需要把发版和检测同步串行执行。但现有针对组件全文件检测,耗时严重影响发版进度。为了解决耗时瓶颈,提出增量检测方案。 oclint-json-compilation-database,提供了 -i 参数,可以传入要检测文件的路径,我们就从这里作为突破口。 方案如下:
比较最近两次或多次发版差异,提取修改的文件路径。只对修改的文件就行检测。可大幅度减少检测耗时,几乎做到对发版无影响。
大概git命令如下:
1.获取提交tag
1 | git tag -l --sort=-version:refname "8.5*" > tag.txt |
按倒序获取8.5分支(分支可指定)下的所有tag写入文件中。通过文件生成tags数组。
2.获取diff文件路径
1 | git diff --name-only ${tags[0]} ${tags[1]} > full_diff.txt |
比较最近两个或多个tag差异,提取出修改的文件路径并写入文件。通过文件生成paths数组,如果paths为空,兜底全量检测。
3.运用OCLint支持分析指定文件路径下源码。把得到的文件路径传入即可。
四,OCLint自动化部署
由于最初的oclint不管是环境安装还是自定义规则编写及发布,都没有规范化。尤其是环境配置还依赖于本地环境,导致服务地址变更都需要重新配置,浪费人力。为了统一及规范化管理iOS静态代码检测,在基础平台的支持下实现了oclint环境自动化,脚本一键安装再无本地概念。OCLint自定义规则流程自动化,通过脚本拉取规则,本地编写规则,脚本编译及发布规则,最终生成二进制安装包。用时只需下载解压即可。
OCLint自定义规则发布自动化
1.克隆托管到我们仓库的oclint源码
2.checkout最新分支,在oclint/oclint-rules/rules/custom路径下 修改编写.cpp源码及CMakeLists文件,提交相应MR同步到远端仓库。
3.执行编译脚本,根据源码自动生成动态库
1 | ./build.sh |
4.执行脚本上传最新规则包
1 | ./publish.sh |
会生成最新的环境安装包:oclint-release-xxxxxx.tar.xz
OCLint环境配置自动化
通过规则自动化我们已经生成了环境安装包,接下来只需下载解压即可脚本如下:
1 | echo "下载并解压oclint环境" |
需要用到oclint的地方只需调用上面脚本即可完成环境配置。
检测报告如下:
已经支持的自定义规则:
写到最后
目前安卓与iOS双端检测方案不同,Infer 是 Facebook 开源的、使用 OCaml 语言编写的静态分析工具,可以对 C、Java 和 Objective-C代码进行静态分析。支持增量分析,但可定制性不强,未来可考虑尝试双端统一使用Infer,但需要考量双端实践难易及成本。