天天看點

代碼靜态分析工具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;
       }

   }
}