白盒测试 代码静态分析工具 Infer 实践

胡刚 · December 04, 2016 · Last by 不二家 replied at November 16, 2018 · 3109 hits
本帖已被设为精华帖!

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

  • captured:每个代码文件转换成中间语言的信息
每个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.
  • specs: 每个方法的分析结果
  • bugs.txt,report.csv,report.json 不同格式的结果集

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;
       }

   }
}

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 25 条回复 时间 点赞
思寒_seveniruby 将本帖设为了精华贴 05 Dec 01:48

加精理由:介绍的专业有深度 对静态分析领域的技术是很好的补充

当时这个 docker 部署方式是我提交的,不过现在已经变更了很多了,许久不玩了,又脱节了。

我们也在工作流中试着接入过 infer,但是扫描速度太慢了,直接造成发布流程阻塞,然后就下架了。请问楼主你们实践平均千行扫描时间大概多久?有没有做过这方面的优化呢?

#4 楼 @simple 太慢是指你们工程分析了多久? 换算下来差不多 521 行/s,优化时间只能对编译命令进行优化,比如 maven, 可以用 infer -- mvn compile --offline -T 1C;

感谢楼主分享,我们在使用的时候平均 2 周扫描一次,所以扫描时间不在考虑中。现在只有 context leak 被研发重视,但修改意愿也不是很大。实际执行起来还是困难重重。

不知 obejectC 代码 怎么检查呢?

#7 楼 @diao2007


infer -- <your build command> 格式,
obj-c具体:infer -- xcodebuild -project projectname

#8 楼 @neven7 为什么我的命令infer --pmd-xml -- xcodebuild -workspace xx.xcworkspace -scheme xx -configuration Release -sdk iphonesimulator clean build

一直报错,然后就不执行,直接跳出了,没有生成 bug.txt 和 report.xml,已经各种谷歌,现在还没有提 issue。
另外再请教你一下,infer 执行结果有没有和 jenkins 集成?如何展示呢?

胡刚 #11 · December 17, 2016 Author

#10 楼 @diao2007 你确认你敲的命令是 infer --pmd-xml -- xcodebuild?为什么你报错是--pod-xml; 你先调试编译执行通过,在加上前置 infer --。

请问
infer -- javac Sort.java
Capturing in javac mode...
env: python2.7: No such file or directory
这个是为啥-。-

补充:我路径下一定有 sort 这个 java 文件的。。。

问下楼主,有尝试过 3.5 的 python 版本下面使用 infer 吗?修改 config.py issues.py make.py。。。。都要修疯了-。-

#7 楼 @diao2007
兄弟,帮忙问下,为什么我在 ubuntu14.04 安装的 infer make 报错?
make[1]: Entering directory /home/fileld/InferTest/infer/facebook-clang-plugins/libtooling'
make[1]: *** No rule to make target
build/SimplePluginASTAction.o', needed by build/FacebookClangPlugin.dylib'. Stop.
make[1]: Leaving directory
/home/fileld/InferTest/infer/facebook-clang-plugins/libtooling'
make: *** [clang_plugin] Error 2

#12 楼 @onionyao
兄弟帮忙问下,infer 安装时编译失败怎么回事啊 ?make 时报错如下
make[1]: Entering directory /home/fileld/InferTest/infer/facebook-clang-plugins/libtooling'
make[1]: *** No rule to make target
build/SimplePluginASTAction.o', needed by build/FacebookClangPlugin.dylib'. Stop.
make[1]: Leaving directory
/home/fileld/InferTest/infer/facebook-clang-plugins/libtooling'
make: *** [clang_plugin] Error 2

Fileld 回复

是安装好了吗?我没有尝试在 3.0 上使用

这款工具有 web 界面形式的结果展示吗?

CC 回复

可以集成到 jenkins pmd 插件解析

不二家 回复

多谢,我先试下

infer 在 android 移动端项目中检测效果是否显著?由于环境约束一直没有尝试

CC 回复

这个要怎么才能 集成到 jenkins pmd 插件解析 我就看报告 没有 xml 格式的文件啊 只有 csv json 和 txt

infer --pmd-xml -- xcodebuild -workspace APP.xcworkspace -scheme APP
Skipping file of module /Users/ios-ci/.jenkins/jobs/APP/workspace/infer-out/report.xml because it's empty.

report.json 正常,怎么没转成 xml,请大神请教

这玩意怎么解决 Error while running epilogue "restoring Maven's pom.xml to its original state":
(Unix.Unix_error "No such file or directory" rename

不二家 回复

大佬,给个联系方式吧,请教一下 infer 的相关问题以及如何集成到 jenkins 展示. 微信:huibooks

huicoding 回复

其实就是将 Infer 检查的报告转成 pmd.xml 的格式,然后用 pmd plugin 展示就行了。很简单。

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up