Static Code Analysis For Android using FindBugs, PMD and Checkstyle
The static code analysis tools are widely used in Java development to improve the codebase and identify potential vulnerabilities along with design flaws. Every tool has its own feature, purpose and strength which helps in increasing the code quality and makes you a better developer. I will be referring Static Analysis Tools as SCA from now on.
Before starting the android integration, it would be great if a gist of what every tool does, should be provided. So, here it goes.
FindBugs
- It analyses Java byte code mainly .classes to find any design flaw and potential bugs.
- It needs compiled code to work around and will eventually be fast since it works on byte code level.
- The major categories in this tool are: Correctness, Bad practice, Dodgy code, Multithreaded Correctness, Performance Malicious, Code Vulnerability, Security Experimental and Internationalization
PMD
- It analyses Abstract Syntax Tree(AST) generated by JavaCC and does not require actual compilation.
- It identifies potential problems mainly dead and duplicated code, cyclomatic complexity, overcomplicated expressions and almost everything which Checkstyle is capable of.
Checkstyle
- It basically analyses source code and looks to improve the coding standard by traversing over simple AST generated by Checkstyle.
- It verifies the source code for coding conventions like headers, imports, whitespaces, formatting etc.
Its time to move onto their Android integration. For every tool, you will be writing gradle scripts that will be tightly integrated with gradle.
findbugs.gradle
apply plugin: 'findbugs'
task findbugs(type: FindBugs) {
description 'Find bugs mainly design flaws, bad practices, multithreaded correctness and code vulnerabilities.'
group 'verification'
excludeFilter = file("$project.rootDir/tools/rules-findbugs.xml")
classes = fileTree("$project.buildDir/intermediates/classes/dev/debug/com/aranoah")
source = fileTree('src/main/java')
effort 'max'
reportLevel = "high"
classpath = files()
reports {
xml.enabled = false
html.enabled = true
html.destination = "$project.buildDir/outputs/findbugs/findbugs.html"
}
}
task: You need to define the task that is to be executed by gradle. Here it is findbugs
excludeFilter: Your custom rules for every SCA tool
classes: The byte code on which the analysis to be done.
html.destination: You need to define the path where the generated report will be stored.
Other fields are self explanatory.
rules-findbugs.xml
<FindBugsFilter>
<!-- Do not check auto-generated resources classes -->
<Match>
<Class name="~.*R\$.*"/>
</Match>
<!-- Do not check auto-generated manifest classes -->
<Match>
<Class name="~.*Manifest\$.*"/>
</Match>
<!-- Do not check auto-generated classes (Dagger puts $ into class names) -->
<Match>
<Class name="~.*Dagger*.*"/>
</Match>
<!-- Do not check for non-initialized fields in tests because usually we initialize them in @Before -->
<Match>
<Class name="~.*Test"/>
<Bug pattern="UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR"
type="UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR"/>
</Match>
<!-- Ignore UPM in lambdas from Retrolambda, FindBugs does not correctly understand them -->
<Match>
<Bug code="UPM"/>
<Class name="~.*\$\$Lambda\$.*"/>
</Match>
<!-- Ignore Butterknife auto-generated classes -->
<Match>
<Class name="~.*\$\$ViewBinder*"/>
</Match>
<Match>
<Class name="~.*\$\$ViewBinder\$InnerUnbinder*"/>
</Match>
</FindBugsFilter>
Similarly add pmd and checkstyle gradle scripts and rules.
pmd.gradle
apply plugin: 'pmd'
task pmd(type: Pmd) {
description 'Identifying potential problems mainly dead code, duplicated code, cyclomatic complexity and overcomplicated expressions'
group 'verification'
ruleSetFiles = files("$project.rootDir/tools/rules-pmd.xml")
source = fileTree('src/main/java')
include '**/*.java'
exclude '**/gen/**'
reports {
xml.enabled = false
html.enabled = true
html.destination = "$project.buildDir/outputs/pmd/pmd.html"
}
}
rules-pmd.xml
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="PMD rules"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>Custom ruleset for 1mg Android application</description>
<exclude-pattern>.*/R.java</exclude-pattern>
<exclude-pattern>.*/gen/.*</exclude-pattern>
<rule ref="rulesets/java/unnecessary.xml"/>
<rule ref="rulesets/java/imports.xml">
<exclude name="TooManyStaticImports"/>
</rule>
<rule ref="rulesets/java/unusedcode.xml"/>
<rule ref="rulesets/java/junit.xml"/>
<rule ref="rulesets/java/logging-java.xml"/>
<rule ref="rulesets/java/braces.xml"/>
<rule ref="rulesets/java/strings.xml"/>
<rule ref="rulesets/java/basic.xml"/>
<rule ref="rulesets/java/design.xml">
<exclude name="ConfusingTernary"/>
</rule>
<rule ref="rulesets/java/typeresolution.xml"/>
<rule ref="rulesets/java/empty.xml/EmptyCatchBlock">
<properties>
<property name="allowCommentedBlocks" value="true"/>
</properties>
</rule>
</ruleset>
checkstyle.gradle
apply plugin: 'checkstyle'
task checkstyle(type: Checkstyle) {
description 'Check code standard'
group 'verification'
configFile file("${project.rootDir}/tools/rules-checkstyle.xml")
source fileTree('src/main/java')
include '**/*.java'
exclude '**/gen/**'
classpath = files()
showViolations true
reports {
xml.enabled = true
html.enabled = true
html.destination = "$project.buildDir/outputs/checkstyle/checkstyle.html"
}
}
Please note that xml.enabled is set to true here. As said earlier, checkstyle works on the AST generated by itself, so it needs to create the tree Checker i.e. checkstyle.xml to work on it. When xml.enabled is set to false, you will get the following error:
Unable to create a Checker: /Users/ashwini.kumar/GitHub/Druid/app/build/reports/checkstyle/checkstyle.xml
rules-checkstyle.xml
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="error"/>
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<!-- Trailing spaces -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<module name="TreeWalker">
<!-- Imports -->
<module name="RedundantImport">
<property name="severity" value="error"/>
</module>
<module name="AvoidStarImport">
<property name="severity" value="error"/>
</module>
<!-- General Code Style -->
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NoFinalizer"/>
<module name="ArrayTypeStyle"/>
<module name="ModifierOrder"/>
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="8"/>
<property name="arrayInitIndent" value="2"/>
</module>
</module>
</module>
All the gradle files and rules are then placed in your root project under the /tools(create a new one) folder. Every rule and script has been hosted in my TvMaze application on Github. Also, you have to consider that the above rules for SCA tool has been configured for my own codebase. You might have to configure the rules and write your exclusions depending on what you actually require and what can be avoided. One final thing that needs to be done, is defining the scripts in your app gradle file.
apply from: "$project.rootDir/tools/findbugs.gradle"
apply from: "$project.rootDir/tools/checkstyle.gradle"
apply from: "$project.rootDir/tools/pmd.gradle"
Now every piece is set properly. You have written your own rules and gradle scripts for every SCA tool. Its time for some action. It is very simple to generate the report now. All you have to do is, run the following commands on the terminal:
./gradlew findbugs
./gradlew pmd
./gradlew checkstyle
When the command is succesfully executed, the report gets generated and you will see a lot of violations from checkstyle and pmd, but these violations will help you improve your code quality. Also, if you are not able to understand what a violation actually is, just click on the link and it will explain the cause of it.
That said, if you are willing to write good code, you must know what a bad code is. Poor code quality is like a disaster waiting to happen.
A code base which follows proper coding convention and guidelines along with well defined static analysis rules, can help in making it more cleaner, understandable, uniform and free of potential bugs that could have been easily missed.
So go ahead, and make your codebase more solid and bug free by integrating static code analysis tools today.
References:
- https://github.com/noveogroup/android-check
- http://continuousdev.com/2015/08/checkstyle-vs-pmd-vs-findbugs/