iOS静态分析:Infer的使用
一、Infer简介
Facebook 的 Infer 是一个静态分析工具。Infer 可以分析 Objective-C, Java 或者 C 代码,报告潜在的问题。任何人都可以使用 Infer 检测应用,这可以将那些严重的 bug 扼杀在发布之前,同时防止应用崩溃和性能低下。Infer 可检查 Android 和 Java 代码中的 NullPointException 和 资源泄露。除了以上,Infer 还可发现 iOS 和 C 代码中的内存泄露。
Infer 已经成为 Facebook 开发流程的一个环节,包括 Facebook Android 和 iOS 主客户端,Facebook Messenger, Instagram 在内的,以及其他影响亿万用户的手机应用,每次代码变更,都要经过 Infer 的检测。
Infer简介.png二、Infer安装
1、Homebrew 安装
brew install infer
如果安装太久,或者安装失败,也可以使用下面的安装包的方式进行安装
2、安装包安装
1)GitHub下载安装包:https://github.com/facebook/infer/releases 选择对应的版本,进行下载。
2)下载后进行解压,解压后会有一个 infer-osx-v1.0.0
目录,v1.0.0是对应的版本号。主执行目录是infer-osx-v1.0.0/lib/infer/infer/bin/
3)设置PATH变量。建议把 Infer 的执行目录加入到环境变量中,这样使用起来会简便一些。当然,你也可以用绝对路径。本文档后续默认执行路径已加入到环境变量中。
open ~/.bash_profile
export PATH="${PATH}:/Users/zjh48/Documents/infer-osx-v1.0.0/lib/infer/infer/bin"
source ~/.bash_profile
注意 export PATH="${PATH}:/Users/zjh48/Documents/infer-osx-v1.0.0/lib/infer/infer/bin"
这个需要写成自己的路径
4)执行 infer --version
,如果输出对应的版本,则说明安装成功
三、Infer的使用
1、Infer基本用法
我们可以新建一个测试工程InferTest
,并在 ViewController.m
中,写上一段代码:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = @"1";
}
@end
首先用cd命令进入InferTest
目录,然后运行以下命令进行编译:
infer -- xcodebuild -target InferTest -configuration Debug -sdk iphonesimulator
执行结束后,终端上面会展示错误信息,在项目所在目录下也会多出build
和infer-out
文件夹,其中report.txt
中的信息和终端上的信息一样
可以看出,我们的 ViewController.m 代码里有1个问题,将代码修改如下:
NSString *str = @"1";
NSLog(@"str = %@",str);
先手动删除build
文件夹(清除编译信息), 再次执行上述命令,重新编译:
infer -- xcodebuild -target InferTest -configuration Debug -sdk iphonesimulator
执行结果:
Infer编译分析没有问题.png恭喜🎉🎉🎉,问题已经消除,以上就是infer的基本用法。
2、可能出现的问题
1)出现xcode-select: error
xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance
这种情况是这种情况是xcodebuild的路径不正确。将路径切换到Xcode的目录下,如下:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/
2)在两次执行编译命令的过程中,发现在没有对代码做任何更改的时候(bug还在),报出BUILD SUCCEEDED的提示:
Infer编译没变化.png根据提示可以看到,此次build并没有分析任何文件。原因涉及到增量分析,默认编译的是有改变的代码,下一小节会详细介绍。
3、增量模式和非增量模式
第一次运行的时候,两种模式是一样的,都会对工程的所有文件进行编译检查,产生检查结果:
增量模式:当已经产生分析结果后(build和infer-out文件夹),再执行编译命令,即为增量模式。如有代码没有改动,则此次不会有编译结果产生,如果代码有新的改动,此次只产生新的编译结果。这种以增量为基准的原则叫做增量模式。
非增量模式:在删除了俩个文件夹的情况下,运行文件,会输出所有的编译信息,即此时处于非增量模式。
增量模式转化为非增量模式:
第1种、直接删除文件夹(build和infer-out文件夹)
第2种、先使用xcodebuild clean 清除一下对应的 target 工程,之后再重新编译分析
xcodebuild -target InferTest -configuration Debug -sdk iphonesimulator clean
4、分析带cocoapods的项目
1)执行命令
先识别 /***.xcworkspace
,再识别 scheme
infer -- xcodebuild -workspace InferTest.xcworkspace -scheme InferTest -configuration Debug -sdk iphonesimulator
这种方法会分享所有的代码,而当我们在项目中使用了很多第三方的时候,其实我们只想让Infer分析我们的代码,而不想分析第三方的代码,不然分析报告中会有很多第三方的issue,看着混乱,这时我们可以用命令行过滤掉关于不想分析的文件。比如用到cocoapods的项目,我们想过滤掉Pods引入的第三方库。
2)过滤三方库
方法一
在命令行里添加过滤条件:INFER_ARGS="--skip-clang-analysis-in-path^[\"Pods\"]"
INFER_ARGS="--skip-clang-analysis-in-path^[\"Pods\"]" infer -- xcodebuild -workspace InferTest.xcworkspace -scheme InferTest -configuration Debug -sdk iphonesimulator
in-path 后面跟的是一个忽略文件或者文件夹的路径数组。
方法二
在工程目录下新建 .inferconfig
文件, 内容如图, 可以过滤掉Pods文件夹下的第三方库, skip-analysis-in-path是一个数组, 想要过滤其他文件, 只需要增加路径即可。
1、in-path 后面跟的是一个忽略文件或者文件夹的路径数组。
2、["Pods"] 代表过滤所有的 Pods 文件
3、["Pods/AFNetworking","Pods/Masonry"] 代表过滤 AFNetworking、Masonry 三方库
4、也可以写个脚本对解析出来的文件进行二次拆分,按组件模块、错误和警告划分
四、Infer工作流程
不管是分析哪种语言,Infer 运行时,分为两个主要阶段:捕获阶段、分析阶段
1、捕获阶段
Infer 捕获编译命令,将文件翻译成 Infer 内部的中间语言。
这种翻译和编译类似,Infer 从编译过程获取信息,并进行翻译。这就是我们调用 Infer 时带上一个编译命令的原因了,比如: infer -- clang -c file.c, infer -- javac File.java。结果就是文件照常编译,同时被 Infer 翻译成中间语言,留作第二阶段处理。特别注意的就是,如果没有文件被编译,那么也没有任何文件会被分析。
Infer 把中间文件存储在结果文件夹中,一般来说,这个文件夹会在运行 infer 的目录下创建,命名是 infer-out/。当然,你也可以通过 -o 选项来自定义文件夹名字
2、分析阶段
在分析阶段,Infer 分析 infer-out/ 下的所有文件。分析时,会单独分析每个方法和函数。
在分析一个函数的时候,如果发现错误,将会停止分析,但这不影响其他函数的继续分析。所以你在检查问题的时候,修复输出的错误之后,需要继续运行 Infer 进行检查,知道确认所有问题都已经修复。
错误除了会显示在标准输出之外,还会输出到文件 infer-out/bug.txt 中,我们过滤这些问题,仅显示最有可能存在的。在结果文件夹中(infer-out),同时还有一个 csv 文件 report.csv,这里包含了所有 Infer 产生的信息,包括:错误,警告和信息。
Infer 的工作流程图.png五、常见扫描错误
1、DIRECT_ATOMIC_PROPERTY_ACCESS。
在代码中使用了使用了一个atomic的成员变量,infer建议我们将atomic修改为nonatomic。由于OC中,属性会被默认设置为atomic属性,我们需要显示将属性声明为nonatomic。关于atomic与nonatomic的区别可以参见文章https://my.oschina.net/linxiaoxi1993/blog/381332
该警告占了大概800个。在代码中主动设置成员变量的nonatomic属性,即可去除警告
2、ASSIGN_POINTER_WARNING
由于在mrc时代,没有weak指针,所以一些view的属性声明是、unsafe__unretain_的形式,在arc中,这个属性被判断为assign,需要将其修改为weak或者strong
3、NULL_DEREFERENCE
空指针的情况。根据具体代码的不同,出现空指针的情况也有所不同。
1)传参为0的情况下。例如代码中,在调用showAlertViewA()时,将tag传参为0,infer检测此处传0,判断为一个NULL空指针,所以爆出警告。这里可以理解为误报,不会出现问题。
2)通过malloc,calloc,realloc等函数申请内存,当内存不足时,有可能会在该函数中返回NULL,如果没有做NULL的判断,则警告
3)在创建NSArray或者NSDictionary时,传入的参数有可能会nil。由于NSArray与NSDictionary不接受空指针,所以在对其addObject或者setObject:forKey: 时需要进行判断一下是否为nil。
4、IVAR_NOT_NULL_CHECKED
在代码中调用block,运行代码时,没有做判空处理。即需要改动为,
if(block){block()}
5、BAD_POINTER_COMPARISON
Implicitly checking whether NSNumber pointer is nil。没有判断一个NSNumber类型的对象是不是空?此处应该是误报。
6、TAINTED_VALUE_REACHING_SENSITIVE_FUNCTION
代码中使用了cookie的value。可以理解为误报
7、PARAMETER_NOT_NULL_CHECKED
传参时没有判断是否为null,加一次判断就可以了
8、STRONG_DELEGATE_WARNING
将一个delegate属性设置为strong的类型。
9、PREMATURE_NIL_TERMINATION_ARGUMENT
没有判断是否为空
10、REGISTERED_OBSERVER_BEING_DEALLOCATED
创建一个对象后,监听了某些通知,但是没有在dealloc中释放该通知。项目中出现这种问题的类,基本都是单例,不会被销毁。
11、MEMORY_LEAK
内存泄露。项目代码全面启动了ARC进行内存管理,在OC层没有扫描出内存泄露。目前扫描出的内存泄露问题都是使用了malloc或者ralloc等c语言内存申请函数,在函数提前return前没有及时free。
参考链接:
infer官网:https://fbinfer.com/docs/getting-started.html
infer中文网站:https://infer.liaohuqiu.net
iOS infer静态分析工具使用:https://www.jianshu.com/p/fd1923cc87eb
iOS-App接入infer静态分析扫描工具:https://www.jianshu.com/p/52f61498e2b2
iOS开发之使用 infer静态代码扫描工具:https://www.cnblogs.com/ZachRobin/p/11280499.html
xcode-select --install 解决方案:https://blog.csdn.net/lucky9322/article/details/79036877