天天看点

代码静态分析工具Infer实践

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 >= 
Python 
Java (only needed for the Java analysis)
gcc >=  or clang >=  (only needed for the C/Objective-C analysis)
autoconf >=  and automake >=  (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 ,请以 根用户 root 运行下面命令:

cd /etc/yum.repos.d/
wget http://download.opensuse.org/repositories/home:ocaml/CentOS_7/home:ocaml.repo
yum install opam
对于 CentOS ,请以 根用户 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.tar.xz

cd infer-linux64-v0

./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. 单个文件

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工程:

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

3-1. 全局工作流:

[[email protected] coveralls-maven-plugin]# infer -- mvn compile

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

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

Found  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  issue

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

Summary of the reports

  RESOURCE_LEAK: 
           

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

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  source files ( classes)
           

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

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

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

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

Found  issue

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

Summary of the reports

  NULL_DEREFERENCE: 
           

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

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

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

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

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


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

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

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

4.Infer其他内部工具

4-1.Eradicate

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

  • 方法中参数和返回
  • 成员声明

4-2. Checkers

语法检查。

4-3. Linters

IOS app语法检查。

5.infer-out结构

[[email protected] 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

 directories,  files
           
  • captured:每个代码文件转换成中间语言的信息
每个java文件都对应有一个目录,形如:
AbstractServiceSetup.javad892b52872fa881fb360f3837218

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.
           
  • specs: 每个方法的分析结果
  • bugs.txt,report.csv,report.json不同格式的结果集

6.自定义检验model

Infer对常用的类库中一些方法都有对应的校验model,以Java为例:

代码静态分析工具Infer实践

如果需要对代码中引用的第三方库方法新建校验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 ;
    }
}
           

则对应新建

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 == );
         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( == num) {
           return "success";
       } else {
           return null;
       }

   }
}