代码静态分析可以提高代码质量和尽早的发现 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 的使用。
infer 只支持 Mac 和 Linux 系统
[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
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
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)
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
Infer 生成的所有文件默认保存在你执行 infer 命令路径下的 infer-out 目录中,可以用-o 参数自定义输出目录。
Inger 工作流包括 2 个动作:capture 和 analysis
capture:infer 通过编译进程将对应的 C、C++、JAVA 和 OBJ-C 代码转换成 Infer 中间语言(OCaml)。
analysis:对 infer-out/captured 的中间数据进行分析后的结果。
默认情况下,每次运行 infer 会删除之前 infer-out 中的数据,即重新分析整个工程。
infer -- javac Hello.java
使用infer -- <your build command>
语法格式
比如你的项目是 maven 的,就执行infer -- mvn compile
infer 支持的 build 系统有:
Gradle、Buck、Maven、Xcodebuild、Make
通过 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 分析累积的改动;
根据提示选择某个具体检测到的问题,打印出该问题对应的代码。
下载一个 maven 工程:
[root@YY14070655 hugang]# git clone https://github.com/trautonen/coveralls-maven-plugin.git
[root@YY14070655 hugang]# cd coveralls-maven-plugin/
Infer 对该工程进行静态代码分析
[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
发现一个可能会导致资源泄露的问题。
每次分析前,需清空之前的 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 代码中的空引用问题。
[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.
严格检查 null point exception, 检查@Nullable注解:
方法中参数和返回
成员声明
infer -a eradicate -- mvn compile
语法检查。
infer -a checkers -- mvn compile
IOS app 语法检查。
infer -a linters -- clang -c Test.m
[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.
Infer 对常用的类库中一些方法都有对应的校验 model,以 Java 为例:
如果需要对代码中引用的第三方库方法新建校验 model,可以通过在infer/models/java/src
下新建方法所在类的包路径和方法所在类名的 java 文件。
比如需要对一个第三方 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
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;
}
}
}