空指针静态代码检查工具Infer

基本介绍

IDEA中提供了Infer Nullity静态代码检查工具,可用于分析Java代码中的潜在的NullPointerException。

注:Nullity检查不局限于Java,但本文只讨论Java。

一个变量的声明(或方法的参数、返回值),可以使用Nullity注解,分别表示变量可能为空、永远非空。

1
2
org.jetbrains.annotations.Nullable
org.jetbrains.annotations.NotNull

被注解的变量,通过Nullity检查可以有效避免NullPointerException异常,例如:

  • 读变量:当调用Nullable注解的变量时,如果不对其进行判空,IDEA就会有警告;调用NotNull注解的变量,如果对其进行不必要的判空也会产生警告。
  • 写变量:NotNull注解的成员变量,如果在类构造时没有为其赋非空值,就会报错。给NotNull注解的变量赋值(或调用函数传参)时,如果所赋值可能为空,也会报错。
  • 方法中NonNull注解的参数,子类继承时也要有NonNull注解。

指定注解

除了使用IDEA自带的注解,IDEA也支持指定其他注解,例如Android开发时,可以使用Android的Nullity注解代替:

1
2
android.support.annotation.Nullable
android.support.annotation.NonNull

Android Studio中默认已经配置了Android的Nullity注解。其他环境下,配置方法可以参考:

More flexible and configurable @Nullable/@NotNull annotations https://blog.jetbrains.com/idea/2011/03/more-flexible-and-configurable-nullublenotnull-annotations/

示例代码

示例代码和在Android Studio中效果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Test {

@Nullable
Object nullable;

// NonNull变量应该在类初始化时赋非空值
@NonNull
Object nonnull;

void f1() {
// NonNull参数不能传空值
f(null);
// NonNull参数不能传可能为空的值
f(nullable);
}

void f(@NonNull Object nonnullArg) {
// NonNull变量不必要的判空
if (nonnullArg == null) {
}
// Nullable变量没做判空
String s = nullable.toString();
}
}

class SubClass extends Test {

// 继承的方法,参数没加NonNull注解
void f(Object nonnullArg) {
}
}

检查

可以在IDEA / Android Studio中,通过Infer Nullity功能检查。

Nullity的问题

由于Java代码中,并不一定所有变量都有Nullity标注,因此Nullity检查并不完全可靠。例如给NonNull变量赋一个可能为空、但没有Nullity标注的成员变量,并不一定能检查出来问题。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A {
Object a;
Object b;

void f(@NonNull Object nonnullArg) {

}

void test() {
// 这里检查不出来问题,因为a没有Nullity注解,也没法从上下文判断a是否可能为空
f(a);

// 这里可以检查出问题,因为可以从上下文推断出b可能为空
b = null;
f(b);
}
}

Kotlin中的Null Safety

在Kotlin中,每个变量定义时,都需要声明是否可能为空。

如果声明变量时,不写问号如下,则变量为NotNull型,不可为空。

In Kotlin, the type system distinguishes between references that can hold null (nullable references) and those that can not (non-null references). For example, a regular variable of type String can not hold null:

1
2
var a: String = "abc"
a = null // compilation error

如果一个变量可能为空,则需要加一个问号如下。

To allow nulls, we can declare a variable as nullable string, written String?:

1
2
var b: String? = "abc"
b = null // ok

Kotlin从语法层面,强制所有变量都要声明为NotNull或Nullable类型。这样通过Nullity检查,代码就不会发生NullPointerException了。

如果将Kotlin代码编译后再反编译成Java,也可以看到其中就是给每个变量使用了Nullity注解。

实现

Infer Nullity检查是由FaceBook开发的静态代码检查工具Infer实现的,可以在命令行执行Infer实现Nullity检查。

Infer http://fbinfer.com/

参考

Android代码重构实战 http://www.10tiao.com/html/209/201706/2651113422/1.html

Android Studio - Infer Nullity? https://stackoverflow.com/questions/22641830/android-studio-infer-nullity

Inferring Nullity https://www.jetbrains.com/help/idea/inferring-nullity.html

Android注解支持(Support Annotations) http://www.flysnow.org/2015/08/13/android-tech-docs-support-annotations.html