LoginSignup
59
73

More than 5 years have passed since last update.

FindBugs 使い方メモ

Posted at

FindBugs とは

Java のソースコードを静的に解析して、バグとなりそうなコードを見つけるツール。

ビルドプロセスの中に組み込んで自動化させることで、ソースコードレビューの手間を減らしたり、そもそも人目では見つけづらいバグコードを事前に検出したりできるようになる。

環境

OS

Windows 10

Java

1.8.0_102

FindBugs

3.0.1

Hello World

インストール

こちら から zip をダウンロードする。

任意の場所に解凍したら完了。

実装

HelloFindBugs.java
public class HelloFindBugs {

    public static void main(String[] args) {
        String text = null;
        System.out.println("text.length=" + text.length());
    }
}
コンパイル
> javac -d classes .\src\HelloFindBugs.java
フォルダ構成
|-src/
|  `-HelloFindBugs.java
`-classes/
   `-HelloFindBugs.class

動かす

  • 先ほど解凍した zip の中の、 bin の下の findbugs.bat を実行する。
  • [ファイル] → [新規プロジェクト] を選択。
  • [Project] に任意の名前(とりあえず hello findbugs とした)を入力。
  • [分析クラスパス] の [追加] ボタンをクリックして、先ほど作成した HelloFindBugs.class が存在するフォルダを選択する。
  • [ソース・ディレクトリー] の [追加] ボタンをクリックして、 HelloFindBugs.java が存在するフォルダを選択する。
  • [分析] ボタンをクリックする。

findbugs.jpg

  • 分析が実行されて、バグの温床となるコードが指摘される。

CUI で動かす

ソースは HelloWorld のときと同じ物 を使用する。

zip を解凍してできたフォルダの下の bin の下にパスを通した状態で、以下のようにコマンドを実行する。

> findbugs -textui -output result.txt .\classes

実行すると、カレントディレクトリに result.txt が出力される。

result.txt
H C NP: ? の null 値を利用しています。HelloFindBugs.main(String[])  参照外しをした箇所 HelloFindBugs.java:[line 5]

xml 形式で出力したい場合は -xml オプションを追加する。
html 形式で出力したい場合は -html オプションを追加する。

xmlで出力する場合の例
> findbugs -textui -output result.xml -xml .\classes

その他使用できるオプションを確認したい場合は、 -textui だけで findbugs を起動すれば、一覧が確認できる。
もしくは こちらを参照

検証の精度を指定する(effort)

> findbugs -textui -effort:max build\classes\main
  • 検証の精度を指定する。
  • max にすると、精度が最大になりより多くのバグを検出できるかもしれない。
  • ただし、メモリ消費が多くなり、検証にかかる時間も長くなる。
  • min にすると、精度は落ちるがメモリ消費は抑えられ、時間も短くなるらしい。
  • 具体的に min, max を切り替えることでどのように検証結果が変わるか確認しようと試みたが、残念ながら明確な差を確認することはできなかった。

main メソッドは特別?

package sample.findbugs;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Main {

    public void method() {
        String text = null;
        System.out.println("text.length=" + text.length());

        try {
            InputStream in = new FileInputStream(new File("./test"));
        } catch (IOException e) {
        }
    }

    public static void main(String[] args) {
        String text = null;
        System.out.println("text.length=" + text.length());

        try {
            InputStream in = new FileInputStream(new File("./test"));
        } catch (IOException e) {
        }
    }
}

main メソッドと、そうでないメソッドを定義している。
中身はそれぞれ同じ。

これを FindBugs に食わせると、以下のようにメッセージが出力される。

H C NP: text の null 値を利用しています。sample.findbugs.Main.method()  参照外しをした箇所 Main.java:[line 12]
H C NP: text の null 値を利用しています。sample.findbugs.Main.main(String[])  参照外しをした箇所 Main.java:[line 22]
M B OS: sample.findbugs.Main.method() は、ストリームのクローズに失敗するかもしれません。  該当箇所 Main.java:[line 15]
M X OBL: sample.findbugs.Main.method() は、java.io.InputStream のクリーンアップに失敗するかもしれません。  Main.java:[line 15] で作成されたリソースのクリーンアップが行われていない
Warnings generated: 4

main メソッドのほうが、出力されているメッセージが少ない。

理由はよくわかっていない。

検知できるバグ

FindBugs が検知できるバグは、以下の一覧で確認できる。

FindBugs Bug Descriptions(日本語)

バグの分類

検知対象のバグは、以下の3つの粒度で分類されている。
後述するフィルター などでバグの検出を制御する際に、この分類を利用して対象のバグを指定することになる。

  1. バグカテゴリー(bug category
  2. バグ略称(bug abbreviations
  3. バグパターン(bug pattern

※「バグ略称」は「バグコード」、「バグパターン」は「バグタイプ」と表記している記事もあったけど、ここでは公式ドキュメント(日本語訳)の言葉を使うことにする。

バグカテゴリー

前述のバグ一覧 の、一番右の列に記載されている Bad practiceCorrectness が、バグカテゴリー。

一番粗い粒度でバグを分類する。

各カテゴリの意味などは以下のブログに詳細な説明がまとめられている。

FindBugsのバグパターン一覧 - 現場のためのソフトウェア開発プロセス - たかのり日記

バグ略称

前述のバグ一覧 の、各バグの説明の先頭に記載されている数文字のアルファベットが、バグ略称になる。

  • BC: equals メソッドは引数の型を仮定するべきではない
  • BIT: ビット演算の符号をチェックする

BCBIT がバグ略称。

バグを中程度で分類している。
代表的なバグパターンの、各単語の頭文字を取ったものがバグ略称となっているので、なんとなく雰囲気で何のチェックかが分かる(NPNullPointerException 関係のバグ、Eqequals() 関係のバグ)

バグパターン

個々のバグを表す、最も粒度の細かい分類。

前述の一覧 では、先頭のテーブルの Description には記載されていない。
個々のバグパターン に飛べば、丸括弧の中にバグパターンの名前が記載されている。
BC_EQUALS_METHOD_SHOULD_WORK_FOR_ALL_OBJECTSBIT_SIGNED_CHECK など)

信頼度(Confidence)

※公式ドキュメントで「信頼度」についての説明を見つけられなかったので、ここに記載しているのは Stackoverflow や実装を見て得た推測です。

検出された問題が本当にバグになりえる可能性を表した指標。
高信頼度(1)、中信頼度(2)、低信頼度(3)の3つから成る。

ver 2.0 より前は「優先度」(Priority)と表現されていたが、それだと優先度の高いバグだけが重要という誤解を生みかねないので、 ver 2.0 から「信頼度」(Confidence)と名前が変更になった。
フィルターの設定ファイルで <Priority> が指定できるが、これは後方互換のために残されている。

バグランク

検出された問題の危険性を表す指標。

1~20 まで存在し、 1~4 が最も恐ろしいバグ(scariest)、5~9 は恐ろしいバグ(scary)、10~14 は厄介なバグ(troubling)、15~20 は不安なバグ(concern)を表している。

信頼度とバグランクは実装内容によって変動する

ドキュメントを見ても、バグパターンごとの信頼度やバグランクは載っていなかった。
どのようにして信頼度が確定するのか FindBugs の実装を見てみたところ、どうやら検証対象の実装内容によって変動するっぽい。

priorityを設定している部分の例(MethodReturnCheck.java)
...
private void sawMethodCallWithIgnoredReturnValue() {
    ...
    int priority = annotation.getPriority();
    if (catchSize <= 1) {
        priority += 2;
    } else if (catchSize <= 2) {
        priority += 1;
    }
    ...

    BugInstance warning = new BugInstance(this, pattern, priority).addClassAndMethod(this).addMethod(callSeen)
            .describe(MethodAnnotation.METHOD_CALLED);
    ...
}
...

priority の値が、実装内容によって加算されたうえで BugInstance を生成している。

実際に、以下のような実装で確認すると priority の変化が確認できる。

Main.java
package sample.findbugs;

public class Main {

    public void method() {
        try {
            new Exception();
        } catch (Exception e) {
        }
    }
}

実行結果

<BugInstance type="RV_EXCEPTION_NOT_THROWN" priority="2" rank="3" abbrev="RV" category="CORRECTNESS">

priority2rank3 になっている。

次に実装を以下のように変更して、再度 FindBugs で検査する。

Main.java
package sample.findbugs;

public class Main {

    public void method() {
        new Exception();
    }
}

実行結果

<BugInstance type="RV_EXCEPTION_NOT_THROWN" priority="1" rank="1" abbrev="RV" category="CORRECTNESS">

priority1 に、 rank1 になった。

フィルター

フィルターを使用することで、クラスや検知するバグを指定して検証の対象外にしたり、逆に対象を絞り込むことができるようになる。

フォルダ構成

|-src/main/java/
|  `-sample/findbugs/
|    `-Main.java
|
|-build/classes/main/
|  `-sample/findbugs/
|    `-Main.class
|
`-myfilter.xml

実装

Main.java
package sample.findbugs;

public class Main {

    private String value = "";

    public void method() {
        value = value;

        if (value == value) {
            new Exception();
        }
    }
}

基本

myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Class name="sample.findbugs.Main" />
    </Match>
</FindBugsFilter>
コマンド実行
> findbugs -textui -include myfilter.xml -output result.html -html build/classes/main

出力結果

findbugs.jpg

説明

  • フィルターの定義は XML で記述する。
  • 一番トップのタグが <FindBugsFilter>
  • 続いて <Match> タグを使って「対象」を定義する。
  • 定義した「対象」だけを処理するのか、それとも逆に除外するのかは、コマンドラインオプションの -include-exclude で指定する。
    • 上記例の場合、 -include myfilter.xml としているので、 myfilter.xml で定義された「対象」だけが処理されるようになる。

※以下の例は、すべて -include を指定して実行した結果です。

カテゴリーで絞り込む

myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Class name="sample.findbugs.Main" />
        <Bug category="CORRECTNESS" />
    </Match>
</FindBugsFilter>

出力結果

findbugs.jpg

  • <Bug> タグを追加して、 category 属性を指定する。
  • カテゴリーの名前はすべて大文字で記述する
    • 一覧に書かれているカテゴリーはスペースなども含んでいるが(Bad practice)、フィルターで指定する場合はスペースをアンダーバー(_)に置き換える(BAD_PRECTICE)。
  • カンマ区切りで複数指定することも可能。

略称で絞り込む

myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Class name="sample.findbugs.Main" />
        <Bug code="SA" />
    </Match>
</FindBugsFilter>

出力結果

findbugs.jpg

  • <Bug> タグの code 属性にバグ略称を指定することで絞り込みが可能。
  • カンマ区切りで複数指定可能。

パターンで絞り込む

myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Class name="sample.findbugs.Main" />
        <Bug pattern="SA_FIELD_SELF_COMPARISON" />
    </Match>
</FindBugsFilter>

出力結果

findbugs.jpg

  • <Bug> タグの pattern 属性にバグパターンを指定することで絞り込みが可能。
  • カンマ区切りで複数指定可能。

カテゴリ・略称・パターンを組み合わせる

myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Class name="sample.findbugs.Main" />
        <Bug category="BAD_PRACTICE"
             code="RV"
             pattern="SA_FIELD_SELF_COMPARISON" />
    </Match>
</FindBugsFilter>

実行結果

findbugs.jpg

  • category, code, pattern は複数を同時に指定することもできる。

信頼度で絞る

絞らない場合の実行結果

findbugs.jpg

信頼度=2 で絞るように設定を変更

myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Class name="sample.findbugs.Main" />
        <Confidence value="2" />
    </Match>
</FindBugsFilter>

実行結果

findbugs.jpg

  • <Confidence> タグで、検知する信頼度を指定できる。
  • 信頼度の値は value 属性で指定する。

バグランクで絞る

絞らない場合の実行結果

  <BugInstance type="ES_COMPARING_STRINGS_WITH_EQ" priority="2" rank="11" abbrev="ES" category="BAD_PRACTICE">
  <BugInstance type="RV_EXCEPTION_NOT_THROWN" priority="1" rank="1" abbrev="RV" category="CORRECTNESS">
  <BugInstance type="SA_FIELD_SELF_ASSIGNMENT" priority="2" rank="3" abbrev="SA" category="CORRECTNESS">
  <BugInstance type="SA_FIELD_SELF_COMPARISON" priority="2" rank="3" abbrev="SA" category="CORRECTNESS">

バグランク 3 と 11 で絞り込むよう設定を変更

myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Class name="sample.findbugs.Main" />
        <Or>
            <Rank value="3" />
            <Rank value="11" />
        </Or>
    </Match>
</FindBugsFilter>
実行結果
  <BugInstance type="ES_COMPARING_STRINGS_WITH_EQ" priority="2" rank="11" abbrev="ES" category="BAD_PRACTICE">
  <BugInstance type="SA_FIELD_SELF_ASSIGNMENT" priority="2" rank="3" abbrev="SA" category="CORRECTNESS">
  <BugInstance type="SA_FIELD_SELF_COMPARISON" priority="2" rank="3" abbrev="SA" category="CORRECTNESS">
  • Rank タグで、検知するバグランクを指定できる。
  • ランクの値は value 属性で指定する。
    • 複数指定する場合は、 <Or> タグなどを利用する。

パッケージで絞る

パッケージ構成

`-sample.findbugs.filter
    |-Zzz.java
    |-aaa
    |   |-Aaa.java
    |   `-ccc
    |       `-Ccc.java
    `-bbb
        `-Bbb.java

各クラスはバグが検出されるように実装している。

基本

myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Package name="sample.findbugs.filter" />
    </Match>
</FindBugsFilter>
実行結果
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.filter.Zzz.method()  該当箇所 Zzz.java:[line 5]
  • <Package> タグで、パッケージを指定して絞り込むことができる。
  • name 属性でパッケージ名を指定する。
  • 指定したパッケージの直下だけが対象になり、サブパッケージは対象にはならない。

正規表現で指定する

myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Package name="~sample\.findbugs\.filter(\..*)?" />
    </Match>
</FindBugsFilter>
実行結果
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.filter.bbb.Bbb.method()  該当箇所 Bbb.java:[line 5]
H C RV: new Exception() をスローしていません。sample.findbugs.filter.aaa.Aaa.method()  該当箇所 Aaa.java:[line 5]
H C RV: new Exception() をスローしていません。sample.findbugs.filter.aaa.ccc.Ccc.method()  該当箇所 Ccc.java:[line 5]
H C RV: new Exception() をスローしていません。sample.findbugs.filter.Zzz.method()  該当箇所 Zzz.java:[line 5]
  • name 属性値の先頭に ~ をつけることで、正規表現によるマッチングが有効になる。

クラスで絞る

Main.java
package sample.findbugs;

public class Main {

    public void method() {
        new Exception();
    }

    private static class InnerClass {

        public void method() {
            new Exception();
        }
    }
}
myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Class name="sample.findbugs.Main" />
    </Match>
</FindBugsFilter>
実行結果
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.Main.method()  該当箇所 Main.java:[line 6]
  • <Class> タグで、クラスを指定して絞り込むことができる。
  • name 属性でクラス名の完全修飾名を指定する。
  • ~ を先頭に書けば、パッケージ同様正規表現での指定が可能。
  • インナークラスは対象にならない。

ソースファイルで絞る

Main.java
package sample.findbugs;

public class Main {

    public void method() {
        new Exception();
    }

    private static class InnerClass {

        public void method() {
            new Exception();
        }
    }
}
myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Source name="Main.java" />
    </Match>
</FindBugsFilter>
実行結果
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.Main.method()  該当箇所 Main.java:[line 6]
H C RV: new Exception() をスローしていません。sample.findbugs.Main$InnerClass.method()  該当箇所 Main.java:[line 12]
  • <Source> タグで、ソースファイルを指定して絞り込むことができる。
  • name 属性でソースファイルの名前を指定する。
  • ~ を先頭に書けば、正規表現での指定が可能。
  • ファイル単位の指定になるので、インナークラスも対象になる。

メソッドで絞る

Main.java
package sample.findbugs;

public class Main {

    public void foo() {
        new Exception();
    }

    public void foo(String text) {
        new Exception();
    }

    public void bar() {
        new Exception();
    }

    public void bar(String text, int i) {
        new Exception();
    }

    public void fizz() {
        new Exception();
    }

    public int buzz() {
        new Exception();
        return 1;
    }
}
myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Or>
            <Method name="foo" />
            <Method name="bar" params="java.lang.String, int" />
            <Method returns="int" />
        </Or>
    </Match>
</FindBugsFilter>
実行結果
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.Main.foo()  該当箇所 Main.java:[line 6]
H C RV: new Exception() をスローしていません。sample.findbugs.Main.foo(String)  該当箇所 Main.java:[line 10]
H C RV: new Exception() をスローしていません。sample.findbugs.Main.bar(String, int)  該当箇所 Main.java:[line 18]
H C RV: new Exception() をスローしていません。sample.findbugs.Main.buzz()  該当箇所 Main.java:[line 26]
  • <Method> タグで、メソッドを指定して絞り込むことができる。
  • name 属性でメソッドの名前を指定する。
    • ~ を先頭に書けば、正規表現での指定が可能。
  • params 属性で、メソッドの引数を指定する。
    • 型名をカンマ区切りで列挙する。
    • 型名は完全修飾クラス名でなければならない(× String, ○ java.lang.String)。
  • returns 属性で、メソッドの戻り値を指定する。

フィールドで絞る

Main.java
package sample.findbugs;

import java.math.BigDecimal;

public class Main {

    class Foo {
        private int fizz;
        private int buzz;
        private BigDecimal hoge;
    }

    class Bar {
        private String fizz;
        private String buzz;
        private BigDecimal fuga;
    }
}
myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Or>
            <Field name="fizz" />
            <Field name="buzz" type="int" />
            <Field type="java.math.BigDecimal" />
        </Or>
    </Match>
</FindBugsFilter>
実行結果
> findbugs -textui -include myfilter.xml build\classes\main
M P UuF: 未使用のフィールド: sample.findbugs.Main$Foo.hoge  該当場所 Main.java
M P UuF: 未使用のフィールド: sample.findbugs.Main$Bar.fizz  該当場所 Main.java
M P UuF: 未使用のフィールド: sample.findbugs.Main$Foo.buzz  該当場所 Main.java
M P UuF: 未使用のフィールド: sample.findbugs.Main$Bar.fuga  該当場所 Main.java
M P UuF: 未使用のフィールド: sample.findbugs.Main$Foo.fizz  該当場所 Main.java
  • <Field> タグで、フィールドを指定して絞り込むことができる。
  • name 属性でフィールドの名前を指定する。
    • ~ を先頭に書けば、正規表現での指定が可能。
  • type 属性で、フィールドの型を指定する。
    • 型名は完全修飾名で記述する。

ローカル変数を絞る

Main.java
package sample.findbugs;

public class Main {
    public void method() {
        int fizz = 1;
        fizz = fizz;

        int buzz = 1;
        buzz = buzz;
    }
}
myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Local name="fizz" />
    </Match>
</FindBugsFilter>
実行結果
> findbugs -textui -include myfilter.xml build\classes\main
M D SA: fizz の自己代入です。sample.findbugs.Main.method()  該当箇所 Main.java:[line 6]
M D DLS: fizz への無効な代入です。sample.findbugs.Main.method()  該当箇所 Main.java:[line 6]
  • <Local> タグで、ローカル変数を指定して絞り込むことができる。
  • name 属性でローカル変数の名前を指定する。
    • ~ を先頭に書けば、正規表現での指定が可能。

論理演算用のタグ

Main.java
package sample.findbugs;

public class Main {
    static class Foo {
        void fizz() {
            new Exception();
        }

        void buzz() {
            new Exception();
        }

        void buzz(String text) {
            new Exception();
        }

        void hoge() {
            new Exception();
        }
    }

    static class Bar {
        void fizz() {
            new Exception();
        }

        void fizz(String text) {
            new Exception();
        }

        void buzz() {
            new Exception();
        }

        void fuga() {
            new Exception();
        }
    }
}
myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <And>
            <Source name="Main.java" />
            <Or>
                <Method name="fizz" />
                <Method name="buzz" />
            </Or>
            <Not>
                <Method params="java.lang.String" />
            </Not>
        </And>
    </Match>
</FindBugsFilter>
実行結果
> findbugs -textui -include myfilter.xml build\classes\main
H C RV: new Exception() をスローしていません。sample.findbugs.Main$Foo.fizz()  該当箇所 Main.java:[line 6]
H C RV: new Exception() をスローしていません。sample.findbugs.Main$Foo.buzz()  該当箇所 Main.java:[line 10]
H C RV: new Exception() をスローしていません。sample.findbugs.Main$Bar.fizz()  該当箇所 Main.java:[line 24]
H C RV: new Exception() をスローしていません。sample.findbugs.Main$Bar.buzz()  該当箇所 Main.java:[line 31]
  • <And> で論理積、 <Or> で論理和、 <Not> で否定が演算できる。

Gradle で使用する

基本

フォルダ構成
|-build.gradle
`-src/main/java/
   `-Main.java
build.gradle
apply plugin: 'java'
apply plugin: 'findbugs'

repositories {
    mavenCentral()
}

Main.java の実装は Hello World と同じ

実行
> gradle findbugsMain
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:findbugsMain FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':findbugsMain'.
> FindBugs rule violations were found. See the report at: file:///.../build/reports/findbugs/main.xml

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 5.456 secs
  • build/reports/findbugs の下に xml 形式で結果が出力される。
  • FindBugs のタスクを単体で実行する場合は、 findbugsMainfindbugsTest を実行する。
  • check タスクを実行するとこれらのタスクも自動で実行されるようになっている。

html で出力する

build.gradle
apply plugin: 'java'
apply plugin: 'findbugs'

repositories {
    mavenCentral()
}

tasks.withType(FindBugs) {
    reports {
        xml.enabled = false
        html.enabled = true
    }
}
  • FindBugs オブジェクトの reports を変更することで、レポートの出力形式を切り替えることができる。
  • enabled にできるのは1つだけなので、 xmlhtml 両方出力ということはできないっぽい。
  • このままだと Jenkins などの CI サービスに連携する xml ファイルが出力できなくなってしまうので、その場合はおとなしく xml で出力して CI サービス上から結果を確認すればいいと思う。

フィルターの指定

フォルダ構成
|-myfilter.xml
|-build.gradle
`-build/classes/main/
   `-sample/findbugs/
       `-Main.class
build.gradle
apply plugin: 'java'
apply plugin: 'findbugs'

repositories {
    mavenCentral()
}

findbugs {
    includeFilter = file('./myfilter.xml')
}
myfilter.xml
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <And>
            <Class name="sample.findbugs.Main" />
            <Method params="int" />
        </And>
    </Match>
</FindBugsFilter>
Main.java
package sample.findbugs;

public class Main {

    public void hoge(int i) {
        new Exception();
    }

    public void fuga(int i) {
        new Exception();
    }

    public void piyo(long l) {
        new Exception();
    }
}
checkタスク実行
> gradle -q check
FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':findbugsMain'.
> FindBugs rule violations were found. See the report at: file:///.../build/reports/findbugs/mai
n.xml

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

実行結果

findbugs.jpg

※実際は xml で出力されているが、分かりやすさのために html による出力を記載している。

  • includeFilter または excludeFilter でフィルターのファイルを指定することができる。
  • 他の詳細な設定については FindBugsExtension の説明 を参照。

アノテーション

アノテーションを使うことで、コードのチェックを宣言的に行うことができるようになる。

build.gradle
apply plugin: 'java'
apply plugin: 'findbugs'

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.google.code.findbugs:annotations:3.0.1'
}
Main.java
package sample.findbugs;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;

@Immutable
public class Main {

    private String name;

    @Nonnull
    public String method() {
        return null;
    }
}

実行結果

findbugs.jpg

  • ビルド時の依存関係に com.google.code.findbugs:annotations を追加して findbugsMain を実行する。
  • すると、コード上で指定したアノテーションに従ってチェックが行われるようになる。
  • どのようなアノテーションが使えるかは、 こちらを参照

JSR-305 : Annotations for Software Defect Detection

FindBugs のアノテーションを依存関係に追加すると、合わせて JSR-305 の jar ファイルも追加される。
JSR-305 は、アノテーションを使ってコードの欠陥を検出する仕様の標準化を目指している(しかし、 2016 年 8 月現在は休止状態になっている)。

JSR-308 : Annotations on Java Types という、同じくアノテーションに関する JSR が存在する。
こちらはアノテーションを記述できる場所を増やそうという JSR で、アノテーションの標準化は行っていない。
この JSR は Java SE 8 で標準仕様として組み込まれている。

JSR-305 は結局頓挫しているものの、 FindBugs 自体はこの JSR-305 で定義されているアノテーションをサポートしており、 javax.annotation.Nonnull などを使用することができる。
一応 FindBugs が独自に定義している edu.umd.cs.findbugs.annotations.NonNull などのアノテーションも存在しているが、これらは軒並み非推奨(Deprecated)になっており、 JSR-305 のアノテーションを使うよう Javadoc に記載されている。

NonNullアノテーションのJavadoc
package edu.umd.cs.findbugs.annotations;

...

/**
 * The annotated element must not be null.
 *
 * Annotated Fields must only not be null after construction has completed.
 * Annotated methods must have non-null return values.
 *
 * @deprecated - use {@link javax.annotation.Nonnull} instead.
 **/
...
@Deprecated
public @interface NonNull {

Eclipse で使用する

確認には Neon を使用した。

プラグインをインストールする

マーケットプレイスで findbugs で検索すれば、 FindBugs Eclipse Plugin というのが出てくるので、それをインストールする。

インストールが完了すると、プロジェクトを右クリックしたときに表示されるメニューに [Find Bugs] が追加される。

findbugs.jpg

検証を実行する

上記メニューの [Find Bugs] -> [Find Bugs] を選択すると、プロジェクトの検証が開始される。

問題のある箇所は、エディタ上で虫アイコンが表示されるようになる。

findbugs.jpg

検証結果の一覧は、 [Bug Explorer] で確認することができる。
[Bug Explorer] が表示されていない場合は、 [Window] → [Show View] → [Other...] → [FindBugs] → [Bug Explorer] と選択して、表示させる。

findbugs.jpg

フィルターを適用する

  • [Window] → [Java] → [FindBugs] を選択。
  • [フィルター・ファイル] タブを開き、 [包含フィルター・ファイル] または [除外フィルター・ファイル] にフィルターファイルのパスを設定する。

findbugs.jpg

IntelliJ で使用する

Community Edition の 2016.2.2 を使用した。

インストール

「FindBugs-IDEA」というプラグインをインストールする。

インストールが完了して再起動すると、ツールバーに FindBugs-IDEA が追加される。

findbugs.jpg

ソースを検証する

FindBugs-IDEA のツールバーを開けば、検証を実行するためのボタンが表示される。
(プロジェクトを右クリックして [FindBugs] を選択しても同じアクションを実行できる)

findbugs.jpg

検証が完了すると、結果が FindBugs-IDEA のパネルに表示され、ソースコード上も問題箇所が赤く表示されるようになる。

findbugs.jpg

findbugs.jpg

フィルターを適用する

findbugs.jpg

FindBugs-IDEA のパネルに [Plugin Preferences] というボタンがあるので、それをクリック。

findbugs.jpg

設定画面が開くので、 [Filter] タブに移動して、 [Include filter files] または [Exclude filter files] にフィルターを定義した xml のパスを設定する。

この状態で再度検証処理を実行すれば、フィルターが適用された状態で検証が行われる。

ちなみに、フィルターの設定情報は .idea/findbugs-idea.xml に保存される。
中を見た感じ、個々のローカル環境には依存しない形になっている模様。

.idea/findbugs-idea.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="FindBugs-IDEA">
    <includeFilterFiles>
      <filter file="$PROJECT_DIR$/myfilter.xml" enabled="true" />
    </includeFilterFiles>
  </component>
</project>

Jenkins と連携する

Jenkins は 2.7.3 を使用した。
Jenkins のインストール方法や、プロジェクトの作り方などは割愛。

ビルド対象のプロジェクトは GitHub に作成した。
https://github.com/opengl-8080/findbugs-jenkins-test

プラグインのインストール

  • [Jenkins の管理] → [プラグインの管理] を開き、 "FindBugs" で検索して "FindBugs Plugin-in" をインストールする。

findbugs.jpg

プロジェクトのビルドで FindBugs の集計を設定する

FindBugs プラグインのインストールが完了すると、 [ビルド後の処理の追加] で [FindBugs警告の集計] が選択できるようになるので、これを追加する。

findbugs.jpg

[集計するファイル] に、 Gradle タスクが実行されることで出力される FindBugs の結果ファイル(XML ファイル)の場所を入力する。

findbugs.jpg

デフォルトだと、ビルドが失敗した場合は警告の集計を行わないようになっている。
FindBugs の検証で問題が発生した場合、ビルドは失敗扱いになるので、そのままだと集計は行われないことになる。

ビルドが失敗した場合も集計を行うようにするため、 [高度な設定] を選択する。
[常に実行] チェックボックスをオンにする。

findbugs.jpg

プロジェクトをビルドする

findbugs.jpg

ビルドを実行すると、ビルド結果に FindBugs の集計結果が出力されるようになる。

findbugs.jpg

FindBugs の検証で出た問題の数に応じてビルド結果を変更する

FindBugs の検証を [常に実行] するというのは、検証以前に片付けるべき重大なビルドエラーが起こった時も無駄に検証処理が走ってしまい、無駄が多くなりそうな気がする。

理想は、ビルドは通った時だけ FindBugs の検証を実行して、問題があったら必要に応じてビルド結果を「失敗」や「不安定」にできるといい。

Gradle の設定を変更する

build.gradle
defaultTasks 'build'

apply plugin: 'java'
apply plugin: 'findbugs'

repositories {
    mavenCentral()
}

findbugs {
    ignoreFailures = true
}
  • findbugs.ignoreFailurestrue を設定する。
  • こうすることで、 FindBugs の検証で問題が発覚してもビルドをエラーにせず続行させられるようになる。

Jenkins のビルド設定を変更する

先ほどオンにした [常に実行] は再びオフに戻す。
しかし、これだけだと FindBugs の警告の集計は行われなくなるので、どんなに問題があってもビルド結果は「成功」になってしまう。

そのため、同じく [高度な設定] に存在する [ビルドステータスの閾値] を設定する。

findbugs.jpg

上記設定の場合、重要度(信頼度のことだと思う)にかかわらず、 3 つより多い警告が検出されたら、ビルド結果は「不安定」になる。
詳しい説明は、下のコメントに記載されている通り。

警告が4つ出るようにしてビルドを実行した結果が以下。

findbugs.jpg

参考

59
73
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59
73