环境

xcode: 8.3
infer: 0.12.0
xcpretty: 0.2.8

infer

facebook 开源的使用 ocaml 开发静态代码检测工具

场景

  • 项目在迭代时需要检测一些错误,人工review不容易发现,如:
    • 无法进入的condition
    • 没有remove的observer
    • 引用循环等
  • 项目工程文件比较多,1500个需要编译的文件以上。这样一次全量检测需要20分钟以上
  • 使用infer分析比较两个不同分支时,由于xcodebuild的机制需要重新编译所有文件,即使只改动了一个文件(两个分支的不同), 所以检测消耗的时间可能会是20分钟 x 2(两次编译),即使infer开启了 --reactive 选项也是如此
  • 项目多人维护,需要在版本回归前发现问题。也就是最好能做到每一次的提交代码,可以及时的获取到增加的分析出的错误和警告

目的

  • 快速静态分析出警告

结论

通过以下步骤可以解决, infer检测庞大iOS项目缓慢的问题。

  • 通过git diff两个分支对比的获取改动文件
  • xcodebuild编译生成 *.hmap后,中断编译 (只为了获取hmap)
  • 直接调用infer内部实现的 clang_wrapper 直接逐个编译改动文件
  • 调用infer的analyze分析代码
  • 调用infer的reportdiff subcommand, 获取增量错误

infer(iOS)工作流程

官方推荐的增量代码检测流程

# go to feature branch if not there already
git checkout feature
# get list of changed files
git diff --name-only origin/feature..origin/master > index.txt
## first run: feature branch
# run infer on the feature branch
infer capture -- xcodebuild -j 4  # assuming a machine with 4 cores
infer analyze --changed-files-index index.txt
# store the infer report
cp infer-out/report.json report-feature.json
## second run: master branch
git checkout master
# run capture in reactive mode so that previously-captured source files are kept if they are up-to-date
infer capture --reactive -- xcodebuild -j 4
infer analyze --reactive --changed-files-index index.txt
# compare reports
infer reportdiff --report-current report-feature.json --report-previous infer-out/report.json

各个步骤的作用

主要说明 captureanalyze 两个步骤的作用, 以及具体的实现

capture (抓取)

infer capture -- xcodebuild -j 4

这个 subcommand 主要用于获取 clang AST(语法树)信息

  • 根据 -- 后的build command xcodebuild, 编译iOS项目,使用xcprtty格式化导出所有的文件的clang build command到json文件, infer的源代码中称这个json为 database
  • 根据 这个 database, 调用infer内置的 clang_wrapper 获取到clang AST并存储为ocaml 序列化文件

analyze(分析)

infer analyze --reactive --changed-files-index index.txt

这个 subcommand 主要用于根据 capture 获取到的 序列化文件 (AST信息),分析出错误, 分析错误分两个阶段

  • analyze, infer内部实现的检查逻辑 (基于两份研究报告实现的代码检测)
  • linter, infer实现对外开放DSL描述语言,根据规则检测(使用者可以自定义添加的规则

速度瓶颈

在上述的流程中,占时间大头且含有不必要的耗时的步骤,主要在,获取database(clang build command的json文件)。

而iOS项目的单文件编译依赖xcodebuild生成的 *.hmap(否则会报错找不到依赖),而*.hmap文件的生成,查了apple的文档之后,并没有发现如何单独生产。

所以也就有了解决方案中的奇葩步骤:

xcodebuild编译 生成 *.hmap 后,中断编译

代码实现

代码实现,后续工作中有需要实现了,再贴上0.0