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中使用增量工作流。核心流程:

  1. 识别变更文件
  2. 启用reactive模式分析
  3. 若需运行多个分析器,建议分离捕获阶段(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等主流构建系统

简单粗暴的实践建议

  1. 开发阶段本地运行infer run快速发现问题
  2. CI流程中配置增量模式(--reactive
  3. 结合构建系统(如Maven/Gradle)实现自动化分析

虽然Infer无法替代动态测试,但作为代码质量防线的前哨,它能有效拦截大量低级错误,让开发者更聚焦于业务逻辑实现。


原始标题:Static Code Analysis Using Infer