- 静态分析
- 代码质量
- 开发工具
【免费下载链接】infer
A static analyzer for Java, C, C++, and Objective-C
导读
本文基于 Infer 官方推荐的 CI 集成方案(website/docs/01-steps-for-ci.md),系统讲解如何在持续集成环境中只分析代码变更、对比两个版本的分析结果,并输出"新增 / 已修复 / 既有"三类差异化报告。读完本文,你将掌握--changed-files-index、--reactive、--incremental-analysis、--eradicate与infer reportdiff的组合用法,能够直接照抄落地到 Git + Gradle/Make 等常见 CI 流水线中。
CI 集成的推荐思路
Infer 官方文档给出的 CI 集成推荐流程是:先确定本次变更涉及的文件,然后以这些文件为起点,以反应式(reactive)模式启动分析,而不是对全量项目做无差别扫描。
如果希望在同一份代码上运行多个分析器(例如默认分析器之外再叠加eradicate、pulse、starvation等),更高效的做法是分离 capture 阶段:只捕获一次编译信息,让所有分析器复用同一份中间结果,避免每个分析器都重复执行构建与翻译。
这一思路建立在 Infer 两阶段工作流之上。任何语言(Java、Objective-C、C/C++)的一次 Infer 运行都分为:
- Capture 阶段:拦截真实编译命令(如
javac、clang、make、gradle),把源码翻译成 Infer 内部中间表示,存放在结果目录infer-out/(可用-o修改)。值得注意的是,若没有文件被编译,也就没有文件会被分析。 - Analysis 阶段:对
infer-out/中的文件逐个函数/方法进行分析,报告输出到标准输出与infer-out/report.txt。
完整的背景可参考 01-infer-workflow.md,其中详细介绍了全局(global)与差异化(differential)两种工作流的适用场景。
通用差异化工作流(Differential Workflow)
假设项目使用 Git:feature是待分析的功能分支,main是主干分支,项目用make构建。官方推荐按以下步骤在 CI 中对比两个分支的分析结果:
# go to feature branch if not there already git checkout feature # get list of changed files git diff --name-only origin/feature..origin/main > index.txt ## first run: feature branch # run infer on the feature branch infer capture -- make -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: main branch git checkout main # run capture in reactive mode so that previously-captured source files are kept if they are up-to-date infer capture --reactive --mark-if-unchanged -- make -j 4 infer analyze --incremental-analysis --changed-files-index index.txt # compare reports infer reportdiff --report-current report-feature.json --report-previous infer-out/report.json步骤一:提取变更文件清单
git diff --name-only origin/feature..origin/main > index.txt利用 Git 本身的能力(而不是 Infer 自己推断)找出两个分支之间的全部变更文件,写入index.txt。该文件随后通过--changed-files-index传给 Infer。
从 infer/src/base/Config.ml 的选项定义可以看到它的语义:
Specify the file containing the list of source files from which reactive analysis should start. Source files should be specified relative to project root or be absolute
即:文件路径要么相对项目根目录,要么使用绝对路径;分析将从这些文件开始,并通过依赖关系波及到受影响的调用方。这里 "reactive analysis should start" 中的 reactive 一词并非巧合——该选项正是为反应式分析服务的:它定义分析的"种子"文件集,而不是让分析器对全部捕获文件做一遍完整扫描。
步骤二:Feature 分支——先 capture 再 analyze
第一次运行拆成两条命令而非一条infer run,原因在于分离 capture 阶段以便复用:
infer capture -- make -j 4:-j 4假设机器有 4 核,让 make 并行编译;Infer 拦截编译调用,完成翻译。此时只做翻译,不进行分析,开销相对可控。infer analyze --changed-files-index index.txt:分析从index.txt列出的文件出发。由于这是全新构建的infer-out/,分析结果即为 feature 分支的完整报告。
之后cp infer-out/report.json report-feature.json把 feature 分支的报告另存下来,供最后比对使用。
步骤三:Main 分支——反应式捕获 + 增量分析
切回main后,两条命令出现了三个关键参数:
--reactive(短选项-r):告诉 Infer不要删除已有的infer-out/。默认情况下,重新运行 Infer 会清空旧的结果目录(这也是全局工作流每次全量分析的来源);而反应式模式保留之前捕获的、内容未变的源文件翻译结果,只有发生变化的文件才需要重新捕获。对于大项目,这能显著缩短第二次运行的时间。--mark-if-unchanged(即手册中的--mark-unchanged-procs):在捕获阶段对新捕获过程做结构等价性比对——若新过程与旧版本结构等价,则标记为"未变更";同时该选项还阻止捕获阶段删除结果数据库,使未变更的结果能在后续增量分析中被复用。从 infer/man/man1/infer-capture.txt 的描述看,它正是为"重跑时尽量保留旧结果"服务的。--incremental-analysis:只对变更文件执行增量分析,它会自动打开--mark-unchanged-procs。手册(infer/man/man1/infer-analyze.txt)同时注明它不兼容--reanalyze与--continue-analysis,使用时需注意。
三者配合的逻辑是:capture 阶段尽量保留未变文件的中间产物,analysis 阶段只重算真正受影响的函数,从而让第二次运行只付出"变更带来的代价"。
步骤四:生成差异化报告
infer reportdiff --report-current report-feature.json --report-previous infer-out/report.jsoninfer reportdiff接收两份标准 JSON 报告:--report-current是最新版本(此处为 feature 分支),--report-previous是基线版本(此处为 main 分支)。它会在结果目录下的infer-out/differential/中生成三个文件,格式与普通 Infer JSON 报告完全一致:
| 文件 | 内容 |
|---|---|
introduced.json | 在 feature 分支中出现、main 中不存在的问题(新增) |
fixed.json | 在 main 中存在、feature 分支中已消失的问题(已修复) |
preexisting.json | 两个分支中都存在的问题(既有) |
语义与 man 页(infer/man/man1/infer-reportdiff.txt)中introduced/fixed/preexisting的定义一一对应。此外infer reportdiff还支持--file-renamings(提供文件重命名映射,避免因文件改名被误判为"新增 + 修复")、--costs-current/--costs-previous(对比成本分析报告)等选项。
这一输出契约在测试基建中也有体现:仓库的 infer/tests/diff.make 定义了默认构建目标即$(INFER_OUT)/differential/introduced.json,并把introduced.json、fixed.json、preexisting.json三个文件逐一用infer report --from-json-report转成文本期望文件(*.exp.test)再与基准比对,验证差异化报告的正确性。
实战示例:Android Gradle + 多分析器
以下 CI 脚本同时运行默认分析与eradicate(空安全)分析器。假设feature为功能分支,main为主干分支:
git diff --name-only origin/feature..origin/main > index.txt infer capture -- ./gradlew --offline assembleDebug infer analyze --fail-on-issue --eradicate --changed-files-index ./index.txt这条脚本浓缩了官方推荐的四个要点:
- 用 Git 找出变更文件:
git diff --name-only,不依赖 Infer 自行比对; - capture 只跑一次,输出复用:后续所有分析器共享同一份
infer-out/; - 叠加额外分析器:
--eradicate与默认分析器并行运行。eradicate是 Infer 的 Java/Kotlin 空安全分析器,检查@Nullable/@NonNull注解是否被正确传播; - 只分析变更文件:
--changed-files-index ./index.txt把分析范围限定在变更文件及其依赖闭包内,大幅缩短 CI 关键路径。
--fail-on-issue让 Infer 在发现问题时以非零退出码结束,这是接入 CI 的必要条件——构建门禁(build gate)据此判定是否阻止合并。需要注意的是,若把infer analyze改为infer run,则--fail-on-issue需要在 capture 之前的全局参数位置传入;分离式写法天然规避了该问题。
为什么不直接infer run?
很多人会问:既然infer run -- gradle build一行就能完成,为什么 CI 脚本要拆成 capture 与 analyze 两步?原因有二:
- 多分析器复用:
infer run的 capture 产物在一次运行结束后仍在infer-out/中保留;若想跑第二个分析器,只需再次infer analyze --<新分析器>,而不必重跑gradle build。对 Gradle 这类增量构建系统,这省去的往往是构建本身的数分钟开销。 - reactive 语义更清晰:
infer analyze单独执行时不会清空infer-out/(它分析的文件就在那里),配合--reactive捕获的既有产物,天然契合"只分析增量"的差异化场景。
结合源码理解背后的机制
--changed-files-index的定位:在 infer/src/base/Config.ml 中定义于Analyze命令的通用参数区,说明它专属于分析阶段。分析器以索引文件中的源文件为根,沿过程间依赖图(Analysis Dependency Graph)展开,相关实现可追溯至 infer/src/backend/AnalysisDependencyGraph.ml 与 infer/src/base/SourceFile.ml。- reactive 捕获的两种推进方式:
infer capture --reactive适合"一批变更一次分析";若在多次编辑之间连续运行,可加--continue让多次捕获累积成一次分析(--continue的语义是"保留之前标记为变更的文件/过程,并叠加本次新增的变更")。官方工作流文档 01-infer-workflow.md 给出了完整示例,且提示"分析当前变更的隔离结果"可以简写为infer run --reactive --continue -- analyze。 - 差异化输出是受测试保护的契约:infer/tests/diff.make 把
introduced/fixed/preexisting三份 JSON 直接作为测试目标,说明这三类文件是 Infer 差分能力对外承诺的稳定接口,CI 脚本可以放心依赖其格式。
落地建议与注意事项
- 首次运行建议从干净项目开始:首次全量 capture 前先
make clean/gradle clean,确保所有编译命令都被捕获(见 01-infer-workflow.md 的 tl;dr)。之后的运行才适合叠加--reactive。 index.txt路径规范:文件内路径应相对项目根目录或为绝对路径,否则--changed-files-index可能解析不到目标源文件。- 多分析器并行:需要多个分析器时,保持"一次 capture + 多次 analyze"的写法;每个
infer analyze用对应开关(如--eradicate、--pulse、--starvation)扩展分析器集合,或用--<checker>-only只跑指定分析器。 - 报告比较:
infer reportdiff的三个输出文件与普通报告同格式,可直接接入后续的告警聚合、PR 评论或看板;文件重命名较多时,用--file-renamings提供映射以减少误报。 - CI 门禁:用
--fail-on-issue结合introduced.json(而非全量报告)决定是否阻断合并,能避免"历史遗留问题阻塞新变更"的困境。
将上述脚本嵌入 Git 钩子、Jenkins/GitHub Actions 或任意 CI 平台,即可获得"变更驱动、增量分析、前后对比"的完整静态分析闭环。
- 静态分析
- 代码质量
- 开发工具
【免费下载链接】infer
A static analyzer for Java, C, C++, and Objective-C
相关推荐
Infer 静态分析器 CI 集成指南:reactive 增量分析与差分报告工作流
Infer 静态分析器 CI 集成指南:reactive 增量分析与差分报告工作流 导读 本文基于 Infer 官方文档《Recommended flow fo
静态分析代码质量开发工具Infer 差分报告全解析:使用 infer reportdiff 计算两次静态分析结果差异
Infer 差分报告全解析:使用 infer reportdiff 计算两次静态分析结果差异 infer reportdiff 是 Meta 开源静态分析工具
静态分析代码质量开发工具Node.js文件系统增量构建:使用node-fs-extra优化构建流程
Node.js文件系统增量构建:使用node fs extra优化构建流程 你是否还在为Node.js项目构建时的文件复制、清理和同步问题烦恼?每次构建都要手动
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考