
Checker Framework とは
Checker Framework は、Java の型システムを拡張して Java プログラム内のエラーを検出・防止します。
特定のエラーについて警告したり、エラーが発生しないことを保証するコンパイル時のツールで、Checker Framework は、様々な種類のチェッカーを提供しています。
ここでは Checker Framework の Nullness Checker の導入方法について説明します。
Nullness Checker は NullPointerExceptionが発生しないことを保証します(@NonNullで注釈が付けられた型を持つ式がnull に評価されることがないこと、および他の式の間接参照を禁止することを保証します)。
Gradle の設定
build.gradle.kts で Checker Framework プラグインを導入します。
plugins {
java
id("org.checkerframework") version "0.6.48"
}
// ...
dependencies {
implementation("org.jspecify:jspecify:1.0.0")
// ...
}
configure<org.checkerframework.gradle.plugin.CheckerFrameworkExtension> {
checkers = listOf(
"org.checkerframework.checker.nullness.NullnessChecker",
)
}
依存に jspecify を追加します。jspecify では @NonNull, @Nullable, @NullMarked, @NullUnmarked というアノテーションが提供されます(Checker Framework は現時点で @NullMarked, @NullUnmarked をサポートしません)(jspecify は、例えば jakarta.annotation.Nonnull や org.springframework.lang.NonNull や org.jetbrains.annotations.NotNull といった乱立するアノテーションを共通化する目的のものです)。
checkers として org.checkerframework.checker.nullness.NullnessChecker チェッカーを指定します。
Checker Framework プラグインは JavaCompile タスクで実行されるため、java プラグインの後に導入する必要があります。
マルチモジュールプロジェクト構成で、Convention Plugins を使っている場合は、buildSrc/build.gradle.kts に以下の依存を追加し、
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
}
dependencies {
implementation("org.checkerframework:org.checkerframework.gradle.plugin:0.6.48")
}
buildSrc/src/main/kotlin/buildlogic.xxx.gradle.kts 側で、以下のようにプラグインを設定します。
plugins {
java
id("org.checkerframework")
}
configure<org.checkerframework.gradle.plugin.CheckerFrameworkExtension> {
checkers = listOf(
"org.checkerframework.checker.nullness.NullnessChecker",
)
}
Gradle 構成キャッシュは無効に
最近の Gradle の init タスクでプロジェクトを作成した場合、gradle.properties が生成され、以下のように構成キャッシュが有効になります。
org.gradle.configuration-cache=true
Checker Framework の Gradle プラグイン 0.6.48 では構成キャッシュがサポートされていないため、以下のように無効化しておく必要があります。
org.gradle.configuration-cache=false
Checker Framework の実行
プラグインが導入できれば、ビルド時に問題が報告されます。というか、compileJava タスク時にChecker Framework が動き、ビルドに失敗します。
例えば、以下のコードを考えます。
import org.jspecify.annotations.NonNull; public class App { public String foo(@NonNull Object ref) { return ref.toString(); } public static void main(String[] args) { new App().foo(null); } }
このコードは、実行すると NullPointerException が発生します。
ビルドすると、以下のようなエラーとなりビルドに失敗します。
$ ./gradlew build
...
> Task :app:compileJava FAILED
App.java:13: error: [argument] incompatible argument for parameter ref of App.foo.
new App().foo(null);
^
found : null (NullType)
required: @Initialized @NonNull Object
1 error
以下のように @Nullable とした場合は、
import org.jspecify.annotations.Nullable; public class App { public String foo(@Nullable Object ref) { return ref.toString(); } // ... }
以下のようなエラーとなりビルドに失敗します。
$ ./gradlew build
...
> Task :app:compileJava FAILED
App.java:9: error: [dereference.of.nullable] dereference of possibly-null reference ref
return ref.toString();
^
1 error
さらに、アノテーション無しで null 呼び出しした場合も、
public class App { public String foo(Object ref) { return ref.toString(); } public static void main(String[] args) { new App().foo(null); } }
以下のようなエラーとなりビルドに失敗します(Nullness Checker では、@NonNull がデフォルトであるため、ほとんど記述されません)。
$ ./gradlew build
...
> Task :app:compileJava FAILED
App.java:13: error: [argument] incompatible argument for parameter ref of App.foo.
new App().foo(null);
^
found : null (NullType)
required: @Initialized @NonNull Object
1 error
以下のように、コード上で null を考慮した実装としていてもビルドエラーになります(チェッカーは、メソッドの型とシグネチャにのみ依存するため)。
public String foo(Object ref) { return (ref == null) ? "" : ref.toString(); }
以下のように @Nullable としてマークする必要があります。
public String foo(@Nullable Object ref) { return (ref == null) ? "" : ref.toString(); }
なお、注釈はフィールドやメソッドの戻り値など、あらゆる型に対して付与できます。
@Nullable Object obj; @Nullable Object getObject(...) { ... }
まとめると、Nullness Checker は以下の場合に警告します。
@NonNull が付与されていない型の式を間接参照すると、NullPointerException が発生する可能性があるため、Nullness Checker が警告します。間接参照は、フィールドにアクセスしたときだけでなく、配列にインデックスが付けられたとき、例外が発生したとき、同期ブロックでロックが取られたときなどにも発生します。
@NonNull が付与された型が null になる場合、Nullness Checker が警告します。
@NonNull 型のフィールドがコンストラクタで初期化されない場合にも警告を発します。
Nullness Checker のその他のアノテーション
Nullness Checker は @Nullable や @NonNull 以外にも多くのアノテーションを扱います。
| 分類 | アノテーション |
|---|---|
| Nullness qualifiers | @Nullable @NonNull, @PolyNull, @MonotonicNonNull |
| Nullness method annotations | @RequiresNonNull @EnsuresNonNull @EnsuresNonNullIf |
| Initialization qualifiers | @Initialized @UnknownInitialization @UnderInitialization |
| Map key qualifiers | @KeyFor |
これらは jspecify の提供するアノテーションではなく、org.checkerframework.checker 配下の Checker Framework 独自のアノテーションです(詳細は割愛します)。
Nullness Checker の警告を抑止したい場合は @SuppressWarnings("nullness") アノテーションを使うことができます。