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