Too many code bugs?

If you need an automated tool to help you or your team find defects in the code, improve code quality and reduce the cost of manual Code Review, then this article is very suitable for you. This article focuses on the integration of SpotBugs and Gradle, introduces the relevant configuration and usage in detail, and provides various means to customize the configuration for your project. Sources and provenance are hyperlinked at key points in the text, enjoy it.

What are SpotBugs?

SpotBugs is an open source Java static analysis tool designed to help developers detect potential flaws and vulnerabilities in code. SpotBugs can scan Java bytecode to find potential problems, such as null pointer references, type conversion errors, unused variables, and more. It can also detect potential security holes in the code, such as SQL injection, XSS attacks, etc. SpotBugs provides a user-friendly GUI and command-line interface that easily integrates with various build tools and IDEs, such as Ant, Maven, Gradle, Eclipse, and IntelliJ IDEA. SpotBugs also supports plug-ins and custom rules, allowing developers to customize configurations based on project-specific needs and standards. More details can be viewed SpotBugs official documentation .

SpotBugs is a branch of FindBugs. It has been improved and upgraded on the basis of FindBugs. It uses more advanced algorithms and techniques to improve the accuracy and efficiency of analysis. SpotBugs also adds support for new Java versions, such as Java 8 and Java 11. SpotBugs also provides a better user interface and command line interface, and supports more build tools and IDE integration.

FindBugs is also a very popular Java static analysis tool, but it has stopped updating. The reason for stopping the update seems to be that the project owner is unwilling to continue to spend time on this project, and code contributors cannot continue to iterate and release new versions because they do not have permission. FindBugs GitHub Homepage of README Two documents are provided in the file Project Status 2016-November and Project Status 2017-September , which describes some of the worries and helplessness of code contributors at that time.

SpotBugs integration with Gradle

Before starting, let’s briefly introduce the dependencies mentioned in this article and the relationship between them, so as to understand the follow-up content:

  • com.github.spotbugs.snom:spotbugs-gradle-plugin is a Gradle plugin that integrates SpotBugs into Gradle builds, generates SpotBugsTask and can be extended through configuration properties.
  • com.github.spotbugs:spotbugs This dependency basically includes all SpotBugs detectors, these detectors implement Bug descriptions The logic of the related Bug detection items mentioned in .
  • com.h3xstream.findsecbugs:findsecbugs-plugin adds a security-related checker based on com.github.spotbugs:spotbugs Bug descriptions#Security Related check items described in .
  • com.github.spotbugs:spotbugs-annotations is an extension/auxiliary tool of Spotbugs. Developers can use the annotations here to make Spotbugs check the code according to our intentions.

spotbugs-gradle-plugin

spotbugs-gradle-plugin is a Gradle plug-in that integrates SpotBugs into the Gradle build, generates SpotBugsTask and provides related configurations to expand, and runs SpotBugsTask to perform inspections, generate reports, and more.

By default, this plugin already contains a spotbugs, so it is generally not necessary to add com.github.spotbugs:spotbugs after applying this plugin. from SpotBugs version mapping You can know the relationship between the spotbugs versions included by default in different versions of spotbugs-gradle-plugin, for example: com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13 contains com.github.spotbugs:spotbugs: 4.7.3.

SpotBugs Gradle plugin The Gradle version is required to be 7.0 or higher, and the JDK version is 1.8 or higher.

This plugin will generate SpotBugsTask for each sourceSets. For example, if there are two sourceSets main and test in the project, the plugin will generate two SpotBugsTask s (spotbugsMain and spotbugsTest).

If you don't want to automatically generate SpotBugsTask, you can use the SpotBugs Base plugin to configure it from scratch, refer to com.github.spotbugs-base.

The generated SpotBugsTask execution requires compiled .class files as input, so they will run after Java compilation. SpotBugs Gradle adds check-related task dependencies, so simply running ./gradlew check will also execute the generated SpotBugsTask.

refer to com.github.spotbugs Add the following content to the build.gradle file to introduce the SpotBugs Gradle plugin:

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13"
  }
}

apply plugin: "com.github.spotbugs"

The plugin provides many optional properties to configure or extend the behavior of the plugin.

The following shows an example of adding spotbugs{} related properties in the build.gradle file. For details, please refer to SpotBugsExtension , which contains the role of each attribute, the value that can be specified, and other relevant information. Most of the attributes specified in spotbugs{} will be used as the default values ​​of the generated SpotBugsTask configuration, which means that all SpotBugsTask attributes can be configured through spotbugs{}.

spotbugs {
    ignoreFailures = false
    showStackTraces = true
    showProgress = false
    reportLevel = 'default'
    effort = 'default'
    visitors = [ 'FindSqlInjection', 'SwitchFallthrough' ]
    omitVisitors = [ 'FindNonShortCircuit' ]
    reportsDir = file("$buildDir/reports/spotbugs")
    includeFilter = file('spotbugs-include.xml')
    excludeFilter = file('spotbugs-exclude.xml')
    onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']
    projectName = name
    release = version
    extraArgs = [ '-nested:false' ]
    jvmArgs = [ '-Duser.language=ja' ]
    maxHeapSize = '512m'
}

In addition to the above methods, SpotBugsTask can also be directly configured to set a task-specific attribute. For example, the following is set separately for the task named spotbugsMain, and the attribute with the same name as spotbugs{} will be overwritten. For details, refer to SpotBugsTask.

spotbugsMain {
    sourceDirs = sourceSets.main.allSource.srcDirs
    classDirs = sourceSets.main.output
    auxClassPaths = sourceSets.main.compileClasspath
  	reports {
        html {
            required = true
            outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html")
            stylesheet = 'fancy-hist.xsl'
        }
    }

    ignoreFailures = false
    showStackTraces = true
    showProgress = false
    reportLevel = 'default'
    effort = 'default'
    visitors = [ 'FindSqlInjection', 'SwitchFallthrough' ]
    omitVisitors = [ 'FindNonShortCircuit' ]
    reportsDir = file("$buildDir/reports/spotbugs")
    includeFilter = file('spotbugs-include.xml')
    excludeFilter = file('spotbugs-exclude.xml')
    baselineFile = file('spotbugs-baseline.xml')
    onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']
    projectName = name
    release = version
    extraArgs = [ '-nested:false' ]
    jvmArgs = [ '-Duser.language=ja' ]
    maxHeapSize = '512m'
}

There will be a paragraph describing the more commonly used or important attributes later.

com.github.spotbugs:spotbugs

Because spotbugs is already included in spotbugs-gradle-plugin, there is no need to introduce this dependency separately under normal circumstances. However, it may be because the default version has bugs, needs to use the same version as IDEA's Spot Bug s plug-in, or the new version of the detector has added practical detection items, etc. We need to specify the version separately. Two methods are provided below. accomplish.

Specify the spotbugs version through toolVersion when configuring spotbugs-gradle-plugin.

spotbugs {
  toolVersion = '4.7.3'
}

Add dependencies to dependencies and specify the dependency version.

dependencies {
    spotbugs 'com.github.spotbugs:spotbugs:4.7.3'
}

findsecbugs-plugin

findsecbugs-plugin is a security vulnerability detection plugin for Spotbugs. It adds its own rule set on the basis of spotbugs, focusing on detecting security-related issues, such as password leaks, SQL injection, XSS, etc., that is Bug descriptions#Security Related check items described in .

dependencies {
    spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0'
}

spotbugs-annotations

spotbugs-annotations is an extension/auxiliary tool of Spotbugs, which provides a lot of annotations, we can add them to the corresponding position of the detected code (such as attributes, methods, formal parameters, local variables) when coding, let Spotbugs follow our intent to check code so that SpotBugs can issue warnings more appropriately. exist Annotations All annotations are listed in , and it should be noted that many of them have been marked as Deprecated to indicate that they have been deprecated.

For example, the following piece of code just outputs the value to the terminal, even if test() passes in null, it will not result in a null pointer. Spotbugs will not issue a warning, but because we added the annotation edu.umd.cs.findbugs.annotations.NonNull to the method, Spotbugs will perform the verification that the input parameter is not allowed to be null according to our intention, thus issuing a warning.

import edu.umd.cs.findbugs.annotations.NonNull;

public class SpotbugsAnnotationsSample {

    public static void main(String[] args) {
        test(null);
    }
    
    public static void test(@NonNull Integer value) {
        // output to terminal
        System.out.println(value);
    }
}

The following is an example of introducing this dependency, refer to Refer the version in the build script , it is also mentioned that starting from spotbugs v4, spotbugs.toolVersion has changed from String to Provider<String>, so please use get() or other methods to reference the actual version.

dependencies {
    compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"
}

SpotBugs Gradle extension properties

includeFilter and excludeFilter

filter( Filter file ) file can define a set of matching rules for matching detection items, inspected classes, and inspected methods, allowing us to customize for the project. After the file is configured, use the attributes provided by SpotBugsTask to specify the path of the filter file to include (includeFilter) or exclude (excludeFilter) the matching content.

The file is in xml format, and each matching rule is written separately in <Match></Match>, using Types of Match clauses The various tags described are combined into the content of the matching rule, in Examples Many examples are given for reference.

Let's focus on this tag, which can be specified by category, code, and pattern Bug descriptions For the check items listed in , multiple parameters are separated by commas, and regular expressions can also be used, starting with the ~ character.

Each check item has a unique pattern, such as XSS_REQUEST_PARAMETER_TO_SEND_ERROR and XSS_REQUEST_PARAMETER_TO_SERVLET_WRITER in the figure below; multiple check items may belong to the same code, such as XSS in the figure below; multiple different codes may belong to the same category. There is a clear hierarchical relationship between the three, so that they can be configured at different granularities.

A simple example and description of using <Bug> is given below, for more examples, please refer to Examples.

<!--Match the two checks specified-->
<Match>
  <Bug pattern="XSS_REQUEST_PARAMETER_TO_SEND_ERROR,XSS_REQUEST_PARAMETER_TO_SERVLET_WRITER" />
</Match>

<!--match all code for XSS check items-->
<Match>
  <Bug code="XSS" />
</Match>

<!--match SECURITY All check items in the directory-->
<Match>
  <Bug category="SECURITY" />
</Match>

visitors and omitVisitors

These two properties can specify to use (visitors) and disable (omitVisitors) checkers respectively, and a checker will contain one or more Bug descriptions The inspection items mentioned in , so the purpose of these two attribute configurations is the same as includeFilter and excludeFilter, so that we can customize the project, just configure from the dimension of the inspector.

exist Detectors All the checkers of Spotbugs are listed in the list, without configuration, Standard detectors The defaults listed in use, Disabled detectors The ones listed in are disabled by default.

Take the checker SynchronizationOnSharedBuiltinConstant For example, from the figure below, we can see that there is a short description under the checker name, and then all the check items included in the checker are listed, and you can see the pattern and code mentioned above, click to redirect to Filter file corresponding location of the documentation. When we configure visitors and omitVisitors, fill in the name of the checker SynchronizationOnSharedBuiltinConstant.

effort

effort is the expected level of configuration code detection. The levels are min, less, more, and max from low to high. The higher the level, the higher the calculation cost and the longer it takes. In this document Effort There is a table inside that clearly lists which inspection contents are included in these levels. The default value of effort is default, which is equivalent to more.

jvmArgs

jvmArgs is used to set JVM parameters, because SpotBugs is written in Java, so it naturally runs on the JVM. We saw -Duser.language=ja in the example, which means to set the language to Japanese, which is the voice displayed by the code analysis results (output to the terminal or report). There are three kinds of English (en default), Japanese (ja), French (fr) in total. GitHub You can see the configuration file for the relevant display text in .

maxHeapSize

maxHeapSize is to set the maximum heap memory size of the JVM, in spotbugsextension#maxHeapSizea It is said that the default value is empty, so the default configuration of Gradle will be used, so it is generally ignored.

onlyAnalyze

onlyAnalyze specifies which code is to be analyzed by SpotBugs. Using this property in large projects to avoid unnecessary analysis may greatly reduce the time required to run the analysis. You can specify a class name or a package name. When specifying a package name, using .* has the same effect as .-, which means to analyze the files in this package and subpackages.

onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']

reportLevel

Spot Bug s divides bugs into three grades P1, P2 and P3 according to the severity from high to low. reportLevel The attribute indicates which level of Bug needs to be displayed in the report. Its configurable values ​​correspond to HIGH, MEDIUM, LOW and DEFAULT, which are defined in Confidence.groovy middle. The default value is DEFAULT is equivalent to MEDIUM, which means to reach the P2 level, which means that the low-level P3 Bug will be ignored,

reports

reports configuration report type, there are html,xml,sarif and text Four, configured in SpotBugsTask (such as spotbugsMain {}), there are not many configurable attributes in the next layer, refer to SpotBugsReport. The following is an example configuration for an html type report:

spotbugsMain {
    reports {
        html {
            required = true
            outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html")
            stylesheet = 'fancy-hist.xsl'
        }
    }
}

Best Practices

configuration

Configure the version in the root directory gradle.properties.

spotbugsGradlePluginVersion=5.0.13
findsecbugsPluginVersion=1.12.0

Add the following configuration in the root directory build.gradle:

buildscript {
    repositories {
        mavenLocal()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
        mavenCentral()
    }
    dependencies {
        classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:${spotbugsGradlePlugin}"
    }
}

apply plugin: "com.github.spotbugs"

repositories {
    mavenLocal()
    maven {
        url "https://plugins.gradle.org/m2/"
    }
    mavenCentral()
}

dependencies {
    compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"
    spotbugsPlugins "com.h3xstream.findsecbugs:findsecbugs-plugin:${findsecbugsPluginVersion}"
}

spotbugs {
    ignoreFailures = false
    showStackTraces = false
    showProgress = false
    excludeFilter = file("${project.rootDir}/code-analysis/spotbugs/exclude-filter.xml")
    extraArgs = [ '-nested:false' ]
}

spotbugsMain {
    reports {
        html {
            required = true
            stylesheet = 'fancy-hist.xsl'
        }
    }
}

If it is a multi-module project configured in this way:

buildscript {
    repositories {
        mavenLocal()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
        mavenCentral()
    }
    dependencies {
        classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:${spotbugsGradlePlugin}"
    }
}

allprojects {
		apply plugin: "com.github.spotbugs"
		
    repositories {
        mavenLocal()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
        mavenCentral()
    }

    dependencies {
        compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"
        spotbugsPlugins "com.h3xstream.findsecbugs:findsecbugs-plugin:${findsecbugsPluginVersion}"
    }

    spotbugs {
        ignoreFailures = false
        showStackTraces = false
        showProgress = false
        excludeFilter = file("${project.rootDir}/code-analysis/spotbugs/exclude-filter.xml")
        extraArgs = ['-nested:false']
    }

    spotbugsMain {
        reports {
            html {
                required = true
                stylesheet = 'fancy-hist.xsl'
            }
        }
    }
}

Create a file /code-analysis/spotbugs/exclude-filter.xml in the root directory of the project, and configure it later as needed.

<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter
        xmlns="https://github.com/spotbugs/filter/3.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">

</FindBugsFilter>

use

Double-click to run the spotbugsMain task in the Gradle window. After the detection is completed, the report address will be printed out in the Run window.

You can also execute ./gradlew spotbugsMain in the Terminal window, you may need to execute chmod +x gradlew first to give the gradlew file execution permission.

If an exception like the following occurs during execution, you can ignore it, but some related analysis will not be executed, and the entire process will not be interrupted, and other irrelevant parts will be checked normally. on GitHub issues#527 Many people reported this problem, but there is no perfect solution for the time being.

The following classes needed for analysis were missing:
apply
test
accept

Interpret the report

The report summary (Summary) counts the number by package and BUG level.

Browse by Categories (Browse by Categories) displays the found defect codes hierarchically according to category, code, and pattern in the inspection items.

Browse by Packages (Browse by Packages) is a different perspective, according to the package name > class name > check item pattern hierarchical display.

The last window, Info, displays the analyzed code files (Analyzed Files), the dependencies used by the analyzed source folders (Source Files) (Used Libraries), the SpotBugs plug-ins used (Plugins) and the exceptions generated during the analysis (Analysis Errors).

filter configuration

Specify filters through includeFilter and excludeFilter ( Filter file ) file is the most flexible, basically any dimension can be controlled. However, it is recommended to only configure which check items to include or exclude, and use category, code, and pattern to configure different granularities.

Use onlyAnalyze to narrow down the code being inspected as a whole, if necessary. If you want to ignore not checking specific classes or methods, you can use annotations @SuppressFBWarnings to mark, it comes from spotbugs-annotations.

other tricks

SpotBugs Links It lists tools that are integrated with SpotBugs or similar, and you can learn about them if you need them.

Tags: Java bug

Posted by breadcom on Thu, 06 Apr 2023 12:08:34 +0930