1. 引言
在软件开发领域,代码质量至关重要,尤其是面对复杂庞大的代码库时。像Infer这样的静态代码分析工具,能帮我们在问题演变成严重故障前,主动检测出潜在的bug和漏洞。
本教程将带你深入代码分析的核心原理,剖析Infer的核心能力,并分享如何将其高效集成到开发流程中的实践经验。
2. 静态代码分析
静态分析是一种无需执行程序就能自动检测源代码的调试技术。它能快速定位潜在缺陷、安全漏洞和可维护性问题。通常由第三方工具(如知名的SonarQube)实现,且自动化后效果显著。
该技术常用于开发早期阶段。代码写完后,立即运行静态分析工具检查是否符合编码规范(包括行业标准或自定义规则)。分析完成后,工具会明确报告代码是否合规。
在企业环境中,它通常作为持续集成(CI)流程的一环。每次代码提交都会触发任务:构建应用、运行测试、分析代码,确保部署前代码的合规性、安全性和稳定性。
3. Infer详解
Infer是用OCaml编写的静态分析工具,支持Java/C/C++/Objective-C。最初由Facebook开发,2015年开源后迅速流行,被众多大型企业采用。
对Java/Android代码,它能检测空指针异常、资源泄漏、并发竞争条件等问题;对C/C++/Objective-C,则能发现空指针问题、内存泄漏、编码规范违规和不可用API调用等。
为高效处理大型代码库,Infer采用了分离逻辑和双推演技术。分离逻辑使其能独立分析代码存储的小片段,避免全内存扫描;双推演则帮助Infer发现代码各部分属性,实现增量分析(仅关注修改部分)。
通过这些技术,Infer能在短时间内从数百万行代码中精准定位复杂问题。
3.1. Infer的两大阶段
无论分析何种语言,Infer都包含两个核心阶段:捕获阶段和分析阶段。
捕获阶段中,Infer拦截编译命令,将待分析文件转换为内部中间语言。该过程类似编译,因此Infer会借用编译信息完成转换。这也是为什么调用Infer时需要附带编译命令:
infer run -- javac File.java
此时文件正常编译,同时Infer生成中间文件存储在infer-out/
目录(默认在执行命令的目录下)。
也可单独执行捕获阶段:
infer capture -- javac File.java
分析阶段中,Infer逐个分析infer-out/
中的文件。每个函数/方法独立分析,若某个方法分析出错,Infer会跳过该实体继续分析其他部分。典型工作流:运行Infer → 修复问题 → 重新运行验证。
检测结果会输出到控制台和infer-out/report.txt
文件,Infer会自动过滤并高亮显示最可能真实存在的bug。
单独执行分析阶段:
infer analyze
3.2. 全局与增量工作流
默认情况下,Infer会删除旧的infer-out/
目录,触发全局工作流——每次全量分析整个项目。
添加--reactive
或-r
参数可保留infer-out/
目录,启用增量工作流。这对使用增量构建系统的移动应用特别实用:只需分析当前变更而非全量代码。⚠️ 实战建议:大型项目务必启用增量模式,否则分析时间可能让你抓狂!
3.3. 项目分析方式
Infer支持多种分析方式:
- 直接调用编译器(如
javac
/clang
) - 使用
gcc
(内部会转为clang
) - 集成主流构建系统
Java项目常用Maven集成方式:
infer run -- mvn <maven target>
Gradle项目集成方式:
infer run -- gradle <gradle task>
3.4. CI环境推荐流程
Infer官方建议在CI中使用增量工作流。核心流程:
- 识别变更文件
- 启用reactive模式分析
- 若需运行多个分析器,建议分离捕获阶段(
infer capture
)供所有分析器复用中间结果
✅ 这样做能显著提升CI效率,避免重复劳动
4. 实战运行Infer
获取Infer有三种方式:二进制包、源码编译或Docker镜像。具体方法参考官方入门指南。
下面用几个Java代码片段演示Infer的检测能力(完整问题列表见这里)。
4.1. 空指针解引用
public class NullPointerDereference {
public static void main(String[] args) {
NullPointerDereference.nullPointerDereference();
}
private static void nullPointerDereference() {
String str = null;
int length = str.length();
}
}
运行Infer后输出:
Analyzed 1 file
Found 1 issue
./NullPointerDereference.java:11: error: NULL_DEREFERENCE
object str last assigned on line 10 could be null and is dereferenced at line 11
9. private static void nullPointerDereference() {
10. String str = null;
11. > int length = str.length();
12. }
13. }
24.
Summary of the reports
NULL_DEREFERENCE: 1
4.2. 资源泄漏
public class ResourceLeak {
public static void main(String[] args) throws IOException {
ResourceLeak.resourceLeak();
}
private static void resourceLeak() throws IOException {
FileOutputStream stream;
try {
File file = new File("randomName.txt");
stream = new FileOutputStream(file);
} catch (IOException e) {
return;
}
stream.write(0);
}
}
Infer检测到资源泄漏:
Analyzed 1 file
Found 1 issue
./ResourceLeak.java:21: error: RESOURCE_LEAK
resource of type java.io.FileOutputStream acquired to stream by call to FileOutputStream(...) at line 17 is not released after line 21
19. return;
20. }
21. > stream.write(0);
22. }
23. }
24.
Summary of the reports
RESOURCE_LEAK: 1
❌ 踩坑提醒:异常分支未关闭流是常见资源泄漏原因
4.3. 除零错误
public class DivideByZero {
public static void main(String[] args) {
DivideByZero.divideByZero();
}
private static void divideByZero() {
int dividend = 5;
int divisor = 0;
int result = dividend / divisor;
}
}
Infer输出除零错误:
Analyzed 1 file
Found 1 issue
./DivideByZero.java:9: error: DIVIDE_BY_ZERO
The denominator for division is zero, which triggers an Arithmetic exception.
6. private static void divideByZero() {
7. int dividend = 5;
8. int divisor = 0;
9. > int result = dividend / divisor;
10. }
Summary of the reports
DIVIDE_BY_ZERO: 1
5. 总结
Infer作为Facebook开源的静态分析工具,通过分离逻辑和双推演技术,实现了对大型代码库的高效分析。其核心优势在于:
- ✅ 多语言支持:Java/C/C++/Objective-C全覆盖
- ✅ 精准检测:空指针、资源泄漏、并发问题等
- ✅ 增量分析:适合CI环境,提升开发效率
- ✅ 灵活集成:支持Maven/Gradle等主流构建系统
简单粗暴的实践建议:
- 开发阶段本地运行
infer run
快速发现问题 - CI流程中配置增量模式(
--reactive
) - 结合构建系统(如Maven/Gradle)实现自动化分析
虽然Infer无法替代动态测试,但作为代码质量防线的前哨,它能有效拦截大量低级错误,让开发者更聚焦于业务逻辑实现。