0.背景

代码静态分析可以提高代码质量和尽早的发现 bugs,减少后期排查问题的时间。

Infer 是 facebook 开源的一款代码静态分析工具,现支持的语言有 Java、Objective-C、C 和 C++; 对 Android 和 Java 代码可以发现 null pointer exceptions 和 resource leaks 等;对 iOS、C 和 C++ 代码可以发现 memory leak 等。

谁在使用,facebook、instagram、UBER、WhatsApp 等等;

在 facebook 内部,由 2 个小团队构建了这个静态分析工具,支持了上千名工程师和百万行级代码。

本文将介绍 Infer 的使用。

1. 安装 Infer

infer 只支持 Mac 和 Linux 系统

1-1. docker 方式

[root@localhost infer_docker]# curl -sSO  https://raw.githubusercontent.com/facebook/infer/master/docker/Dockerfile

[root@localhost infer_docker]# curl -sSO https://raw.githubusercontent.com/facebook/infer/master/docker/run.sh

sh run.sh

1-2. Mac

hugangdeMacBook-Pro:~ hugang$ brew update
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- mach (LoadError)

解决办法(重装brew):
hugangdeMacBook-Pro:~ hugang#  ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"

hugangdeMacBook-Pro:~ hugang# ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"


hugangdeMacBook-Pro:~ hugang$ brew install infer

1-3. Linux 下 release 版本

infer 的依赖:https://github.com/facebook/infer/blob/master/INSTALL.md#pre-compiled-versions

Infer dependencies for Linux

Here are the prerequisites to be able to compile Infer on Linux. This is required to be able to use the release (faster), or to compile everything from source (see the end of this document).

opam >= 1.2.0
Python 2.7
Java (only needed for the Java analysis)
gcc >= 4.7.2 or clang >= 3.1 (only needed for the C/Objective-C analysis)
autoconf >= 2.63 and automake >= 1.11.1 (if building from git)

其中依赖 opam(OPAM is a source-based package manager for OCaml) 安装如下:

http://opam.ocaml.org/doc/Install.html#FedoraCentOSandRHEL

http://software.opensuse.org/download.html?project=home%3Aocaml&package=opam

对于 CentOS 7,请以 根用户 root 运行下面命令:

cd /etc/yum.repos.d/
wget http://download.opensuse.org/repositories/home:ocaml/CentOS_7/home:ocaml.repo
yum install opam
对于 CentOS 6,请以 根用户 root 运行下面命令:

cd /etc/yum.repos.d/
wget http://download.opensuse.org/repositories/home:ocaml/CentOS_6/home:ocaml.repo
yum install opam

依赖解决后,下载 infer 的 release 版本:
https://github.com/facebook/infer/releases/tag/v0.9.4.1

wget https://github.com/facebook/infer/releases/download/v0.9.4.1/infer-linux64-v0.9.4.1.tar.xz

tar xf infer-linux64-v0.9.4.1.tar.xz

cd infer-linux64-v0.9.4.1

./build-infer.sh

vim /etc/profile添加infer命令的路径

export PATH=${infer安装路径}/infer/bin:$PATH

source /etc/profile

2.Infer 工作流

Infer 生成的所有文件默认保存在你执行 infer 命令路径下的 infer-out 目录中,可以用-o 参数自定义输出目录。

Inger 工作流包括 2 个动作:capture 和 analysis

capture:infer 通过编译进程将对应的 C、C++、JAVA 和 OBJ-C 代码转换成 Infer 中间语言(OCaml)。

analysis:对 infer-out/captured 的中间数据进行分析后的结果。

2-1. 全局工作流

默认情况下,每次运行 infer 会删除之前 infer-out 中的数据,即重新分析整个工程。

2-1-1. 单个文件

infer -- javac Hello.java

2-1-2. 工程

使用infer -- <your build command>语法格式

比如你的项目是 maven 的,就执行infer -- mvn compile

infer 支持的 build 系统有:
Gradle、Buck、Maven、Xcodebuild、Make

2-2. 差异化工作流

通过 Infer 的 reactive 模式只分析改动的代码。

1. mvn clean

2. infer -a capture -- mvn compile

3. ### 改动代码操作1

4. infer --reactive -- mvn compile

5. ### 改动代码操作2

6. infer --reactive --continue -- mvn compile

第 4 行分析代码操作 1,第 6 行分析代码操作 1 和 2;

--reactive 只分析上一次改动;

--reactive --continue 分析累积的改动;

2-3. 交互式查看报告:inferTraceBugs

根据提示选择某个具体检测到的问题,打印出该问题对应的代码。

3. 实例操作

下载一个 maven 工程:

[root@YY14070655 hugang]# git clone https://github.com/trautonen/coveralls-maven-plugin.git
[root@YY14070655 hugang]# cd coveralls-maven-plugin/

Infer 对该工程进行静态代码分析

3-1. 全局工作流:

[root@YY14070655 coveralls-maven-plugin]# infer -- mvn compile

Capturing in maven mode...
Translating 54 source files (62 classes)
Starting analysis...

legend:
  "F" analyzing a file
  "." analyzing a procedure

Found 54 source files in /data0/hugang/coveralls-maven-plugin/infer-out
FFFFFFFFFFFF................................F..................................F....F...........F.F.......F....F...F.FF.........F...F.F..F....F.............FF......................F...F.......F.F..........F................FF..F................................FFF........FFF.............F....F..F.......F....F......FFF.......FF.F........................................................

Found 1 issue

src/main/java/org/eluder/coveralls/maven/plugin/domain/GitRepository.java:61: error: RESOURCE_LEAK
   resource of type org.eclipse.jgit.revwalk.RevWalk acquired by call to new() at line 61 is not released after line 61
  59.       private Git.Head getHead(final Repository repository) throws IOException {
  60.           ObjectId revision = repository.resolve(Constants.HEAD);
  61. >         RevCommit commit = new RevWalk(repository).parseCommit(revision);
  62.           Git.Head head = new Git.Head(
  63.   

Summary of the reports

  RESOURCE_LEAK: 1

发现一个可能会导致资源泄露的问题。

3-2. 差异化工作流:

每次分析前,需清空之前的 class 文件,即执行mvn clean

[root@YY14070655 coveralls-maven-plugin]# mvn clean
[root@YY14070655 coveralls-maven-plugin]# infer -a capture -- mvn compile
Capturing in maven mode...
Translating 54 source files (62 classes)

进行差异化分析,在该工程中新建一个 NullPointException.java 文件

[root@YY14070655 plugin]# vim NullPointException.java 
public class NullPointException {
    public static void main(String[] args) {
        String str = null;
        if(str.equals("excepion")) {
            System.out.println("woo");
        }
    }
}
[root@YY14070655 coveralls-maven-plugin]# infer --reactive -- mvn compile
Capturing in maven mode...
Translating 54 source files (62 classes)
Starting analysis...

legend:
  "F" analyzing a file
  "." analyzing a procedure

Found 55 source files in /data0/hugang/coveralls-maven-plugin/infer-out
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF..FFFFFFFFFFFFFFFFFF

Found 1 issue

src/main/java/org/eluder/coveralls/maven/plugin/NullPointException.java:5: error: NULL_DEREFERENCE
  object str last assigned on line 4 could be null and is dereferenced at line 5
  3.   
  4.    String str = null;
  5. >  if(str.equals("excepion")) {
  6.                System.out.println("woo");
  7.   

Summary of the reports

  NULL_DEREFERENCE: 1

只分析出本次新增的 NullPointException.java 代码中的空引用问题。

3-3. 交互式查看问题:

[root@YY14070655 coveralls-maven-plugin]# inferTraceBugs
0. src/main/java/org/eluder/coveralls/maven/plugin/NullPointException.java:5: error: NULL_DEREFERENCE
     object str last assigned on line 4 could be null and is dereferenced at line 5

Auto-selecting the only report.
Choose maximum level of nested procedures calls (default=max): 

src/main/java/org/eluder/coveralls/maven/plugin/NullPointException.java:5: error: NULL_DEREFERENCE
  object str last assigned on line 4 could be null and is dereferenced at line 5
Showing all 3 steps of the trace


src/main/java/org/eluder/coveralls/maven/plugin/NullPointException.java:2: start of procedure main(...)
1.   public class NullPointException {
2. >     public static void main(String[] args) {
3.   
4.   

src/main/java/org/eluder/coveralls/maven/plugin/NullPointException.java:4: 
2.       public static void main(String[] args) {
3.   
4. >    String str = null;
5.      if(str.equals("excepion")) {
6.   

src/main/java/org/eluder/coveralls/maven/plugin/NullPointException.java:5: 
3.   
4.      String str = null;
5. >    if(str.equals("excepion")) {
6.                  System.out.println("woo");
7.   

4.Infer 其他内部工具

4-1.Eradicate

严格检查 null point exception, 检查@Nullable注解:

infer -a eradicate -- mvn compile

4-2. Checkers

语法检查。

infer -a checkers -- mvn compile

4-3. Linters

IOS app 语法检查。

infer -a linters -- clang -c Test.m

5.infer-out 结构

[root@YY14070655 infer-out]# tree -L 1
.
├── attributes
├── backend_stats
├── bugs.txt
├── captured
├── frontend_stats
├── multicore
├── proc_stats.json
├── report.csv
├── reporting_stats
├── report.json
├── sources
├── specs
└── toplevel.log

8 directories, 5 files

每个java文件都对应有一个目录,形如:
AbstractServiceSetup.java.0811d892b52872fa881fb360f3837218

The files contain serialized OCaml data structures. 

每个目录都包含cfg和cg文件,形如:
AbstractServiceSetup.java.aa00cf389422c87fbf8271b732c88233.cfg  

AbstractServiceSetup.java.aa00cf389422c87fbf8271b732c88233.cg

The .cfg file contains a control flow graph for each function or method implemented in the file. 

The file .cg contains the call graph of the functions defined or called from that file.

6.自定义检验 model

Infer 对常用的类库中一些方法都有对应的校验 model,以 Java 为例:
这里写图片描述
如果需要对代码中引用的第三方库方法新建校验 model,可以通过在infer/models/java/src下新建方法所在类的包路径和方法所在类名的 java 文件。

6-1. 新建 model 文件

比如需要对一个第三方 lib:com.github.neven7 中对 Model 类中方法 getNumber() 新增校验 model,原代码如下:

package com.github.neven7

public class Model {
    public int getNumber() {
        return 0;
    }
}

则对应新建infer/models/java/src/com/github/neven7/Model.java文件:

package com.github.neven7

import com.facebook.infer.models.InferBuiltins;
import com.facebook.infer.models.InferUndefined;

public class Model {
      public int getNumber() {
         // 创建一个不确定的int
         int num =  InferUndefined.int_undefined();
         // 假设num为0 
         InferBuiltins.assume(num == 0);
         return num;
      }
}

新建的这个 model 告诉使用了第三方 lib 中对 Model 类中方法 getNumber() 只返回 0。

com.facebook.infer.models.InferBuiltins 和 com.facebook.infer.models.InferUndefined 源码:
https://github.com/facebook/infer/blob/0.9.4/infer/models/java/builtins/com/facebook/infer/builtins/InferUndefined.java
https://github.com/facebook/infer/blob/0.9.4/infer/models/java/builtins/com/facebook/infer/builtins/InferBuiltins.java

6-2. 重新编译 Infer

bash make -C infer

如果源码未对 getNumber() 新建 model, Infer 对下面 Test.java 进行静态分析时会报 null pointer exception;如果新增了上述的 model,Infer 不会报 null pointer exception,因为 model 知道 getNumber() 只返回 0。

import com.github.neven7

public class Test {

   public String getState() {
       int num = new Model().getNumber();
       if(0 == num) {
           return "success";
       } else {
           return null;
       }

   }
}


↙↙↙阅读原文可查看相关链接,并与作者交流