FindBugs, Part 1: Improve the quality of your code

Why and how to use FindBugs

Static analysis tools promise to find existing bugs in your code without requiring much effort on the part of the developer. Of course, if you've been programming for long, you know those promises don't always pan out. Even so, good static analysis tools are a valuable addition to your toolbox. In this first of a two-part series, Senior Software Engineer Chris Grindstaff looks at how FindBugs can help improve the quality of your code and eliminate bugs lying in wait. Don't miss Part 2 of this series to get the final part of the story.

Share:

Chris Grindstaff (chris@gstaff.org), Software Engineer, IBM

Chris Grindstaff is a Senior Software Engineer at IBM in Research Triangle Park, North Carolina. Chris wrote his first program at the age of 7, when he convinced his grade school teacher that "typing" sentences would be just as onerous a punishment as writing them by hand. Chris is currently interested in a variety of open-source projects. He has worked extensively with Eclipse and authored several popular Eclipse plug-ins, which may be found on his Web site. You can contact Chris at cgrinds@us.ibm.com or chris@gstaff.org.



25 May 2004

Also available in Japanese

One of the problems with code quality tools is that they tend to overwhelm developers with problems that aren't really problems -- that is, false positives. When false positives occur, developers learn to ignore the output of the tool or abandon it altogether. The creators of FindBugs, David Hovemeyer and William Pugh, were sensitive to this issue and strove to reduce the number of false positives they report. Unlike other static analysis tools, FindBugs doesn't focus on style or formatting; it specifically tries to find real bugs or potential performance problems.

What is FindBugs?

FindBugs is a static analysis tool that examines your class or JAR files looking for potential problems by matching your bytecodes against a list of bug patterns. With static analysis tools, you can analyze software without actually running the program. Instead the form or structure of the class files are analyzed to determine the program's intent, often using the Visitor pattern (see Resources). Figure 1 shows the results of analyzing an anonymous project (its name has been withheld in order to protect the horribly guilty):

Figure 1. FindBugs UI
Graphical representation of Configure Detectors

Let's take a look at some of the problems that FindBugs can detect.


Examples of problems found

The following list doesn't include all the problems FindBug might find. Instead, I've focused on some of the more interesting ones.

Detector: Find hash equals mismatch
This detector finds several related problems, all centered around the implementation of equals() and hashCode(). These two methods are very important because they're called by nearly all of the Collections-based classes -- List, Maps, Sets, and so on. Generally, this detector finds two different types of problems -- when a class:

  • Overrides Object's equals() method, but not its hashCode or vice-versa.
  • Defines a co-variant version of the equals() or compareTo() method. For example, the Bob class defines its equals() method as boolean equals(Bob), which overloads the equals() method defined in Object. Because of the way the Java code resolves overloaded methods at compile-time, the version of the method defined in Object will almost always be the one used at runtime, not the one you defined in Bob (unless you explicitly cast the argument to your equals() method to type Bob). As a result, when one of the instances of this class is put into any of the collection classes, the Object.equals() version of the method will be used, not the version defined in Bob. In this case, the Bob class should define an equals() method that accepts an argument of type Object.

Detector: Return value of method ignored
This detector looks for places in your code where the return value of a method is ignored when it shouldn't be. One of the more common instances of this scenario is found when invoking String methods, such as in Listing 1:

Listing 1. Example of ignored return value
1  String aString = "bob";
2  b.replace('b', 'p');
3  if(b.equals("pop"))

This mistake is pretty common. At line 2, the programmer thought he'd replaced all of the b's in the string with p's. He did, but he forgot that strings are immutable. All of these types of methods return a new string, never changing the receiver of the message.

Detector: Null pointer dereference and redundant comparisons to null
This detector looks for two types of problems. It looks for cases where a code path will or could cause a null pointer exception, and it also looks for cases in which there is a redundant comparison to null. For example, if both of the compared values are definitely null, they're redundant and may indicate a coding mistake. FindBugs detects a similar problem when it's able to determine that one of the values is null and the other one isn't, as shown in Listing 2:

Listing 2. Null pointer examples
1  Person person = aMap.get("bob");
2  if (person != null) {
3      person.updateAccessTime();
4  }
5  String name = person.getName();

In this example, if the Map on line 1 does not contain the person named "bob," a null pointer exception will result on line 5 when the person is asked for his name. Because FindBugs doesn't know if the map contains "bob" or not, it will flag line 5 as a possible null pointer exception.

Detector: Field read before being initialized
This detector finds fields that are read in constructors before they're initialized. This error is often caused by mistakenly using a field's name instead of a constructor argument -- although not always, as Listing 3 shows:

Listing 3. Reading a field in a constructor before it's initialized
1  public class Thing {
2      private List actions;
3      public Thing(String startingActions) {
4          StringTokenizer tokenizer = new StringTokenizer(startingActions);
5          while (tokenizer.hasMoreTokens()) {
6              actions.add(tokenizer.nextToken());
7          }   
8      }
9  }

In this example, line 6 will cause a null pointer exception because the variable actions has not been initialized.

These examples are only a small sampling of the types of problems that FindBugs detects (see Resources for more). At the time of this writing, FindBugs comes with a total of 35 detectors.


Getting started with FindBugs

To run FindBugs, you will need a Java Development Kit (JDK), version 1.4 or higher, although it can analyze the class files created by older JDKs. The first thing to do is download and install the latest release of FindBugs -- currently 0.7.1 (see Resources). Fortunately, the download and installation is pretty straightforward. After downloading the zip or tar, unzip it into a directory of your choosing. That's it -- the install is finished.

Now that it's installed, let's run it on a sample class. As is often the case with articles, I will speak to the Windows users and assume that those of the Unix persuasion can deftly translate and follow along. Open a command prompt and go to the directory in which you installed FindBugs. For me, that's C:\apps\FindBugs-0.7.3.

In the FindBugs home directory, there are a couple of directories of interest. The documentation is located in the doc directory, but more important for us, the bin directory contains the batch file to run FindBugs, which leads me to the next section.


Running FindBugs

Like most tools these days, you can run FindBugs in multiple ways -- from a GUI, from a command line, using Ant, as an Eclipse plug-in, and using Maven. I'll briefly mention running FindBugs from the GUI, but I'll primarily focus on running it from Ant and the command line. Partly that's because the GUI hasn't caught up with all of the command line options. For example, currently you can't specify filters to include or exclude particular classes in the UI. But the more important reason is because I think FindBugs is best used as an integrated part of your build, and UIs don't belong in automated builds.

Using the FindBugs UI

Using the FindBugs UI is straightforward, but a couple of points deserve some elaboration. As Figure 1 demonstrates, one of the advantages of using the FindBugs UI is the description provided for each type of detected problem. Figure 1 shows the description for the bug Naked notify in method. Similar descriptions are provided for each bug pattern, which is extremely useful when you're first becoming acquainted with the tool. Equally useful is the Source code tab in the lower pane of the window. If you tell FindBugs where to find your source, it will highlight the offending line of code when you switch to the appropriate tab.

It's also important to mention that if you choose xml as your output option when running FindBugs as an Ant task or from the command line, you can load the results of a previous run into the UI. Doing so is a great way to leverage the advantages of the command-line-based tooling and the UI tooling at the same time.

Running FindBugs as an Ant task

Let's take a look at how to use FindBugs from an Ant build script. First copy the FindBugs Ant task to Ant's lib directory so that Ant is made aware of the new task. Copy FIND_BUGS_HOME\lib\FindBugs-ant.jar to ANT_HOME\lib.

Now take a look at what you need to add to your build script to use the FindBugs task. Because FindBugs is a custom task, you'll need to use the taskdef task so that Ant knows which classes to load. Do that by adding the following line to your build file:

<taskdef name="FindBugs" classname="edu.umd.cs.FindBugs.anttask.FindBugsTask"/>

After defining taskdef, you can refer to it by its name, FindBugs. Next you'll add a target to the build that uses the new task, as shown in Listing 4:

Listing 4. Creating a FindBugs target
1  <target name="FindBugs" depends="compile">
2      <FindBugs home="${FindBugs.home}" output="xml" outputFile="jedit-output.xml">
3          <class location="c:\apps\JEdit4.1\jedit.jar" />
4          <auxClasspath path="${basedir}/lib/Regex.jar" />
5          <sourcePath path="c:\tempcbg\jedit" />
6      </FindBugs>
7  </target>

Let's take a closer look at what's going on in this code.

Line 1: Notice that the target depends on the compile. It's important to remember that FindBugs works on class files, not source files, so making the target depend on the compile target ensures that FindBugs will be running across the up-to-date class files. FindBugs is flexible about what it will accept as input, including a set of class files, JAR files, or a list of directories.

Line 2: You must specify the directory that contains FindBugs, which I did using an Ant property like this:

<property name="FindBugs.home" value="C:\apps\FindBugs-0.7.3" />

The optional attribute output specifies the output format that FindBugs will use for its results. The possible values are xml, text, or emacs. If no outputFile is specified, then FindBugs prints to standard out. As mentioned previously, the XML format has the added advantage of being viewable within the UI.

Line 3: The class element is used to specify which set of JARs, class files, or directories you want FindBugs to analyze. To analyze multiple JARs or class files, specify a separate class element for each. The class element is required unless the projectFile element is included. See the FindBugs manual for more details.

Line 4: You list your application's dependencies by using the nested element auxClasspath. These are classes that your application needs, but you don't want FindBugs to analyze. If you don't list your application's dependencies, FindBugs will still analyze your classes as well as it can, but it will complain when it is unable to find one of the missing classes. as with the class element, you can specify multiple auxClasspath elements in the FindBugs element. The auxClasspath element is optional.

Line 5: If the sourcePath element is specified, the path attribute should indicate a directory that contains your application's source code. Specifying the directory allows FindBugs to highlight the source code in error when viewing the XML results in the GUI. This element is optional.

That covers the basics. Let's fast forward several weeks.

Filters

You've introduced FindBugs to your team and have been running it as a part of your hourly/nightly build process. As the team has become more acquainted with the tool, you've decided that some of the bugs being detected aren't important to your team, for whatever reason. Perhaps you don't care if some of your classes return objects that could be modified maliciously -- or maybe, like JEdit, you have a real honest-to-goodness, legitimate reason to invoke System.gc().

You always have the option of "turning off" a particular detector. On a more granular level, you could exclude certain detectors from finding problems within a specified set of classes or even methods. FindBugs offers this granular control with exclude and include filters. Exclude and include filters are currently supported only in the command-line or Ant versions of FindBugs. As the name implies, you use exclude filters to exclude the reporting of certain bugs. The less popular, but still useful, include filters can be used to report targeted bugs only. The filters are defined in an XML file. They may be specified at the command-line with an exclude or include switch or by using the excludeFilter and includeFilter in your Ant build file. In the examples below, assume that the exclude switch was used. Also note in the discussion below that I use "bugcode," "bug," and "detector" somewhat interchangeably.

Filters can be defined in a variety of ways:

  • Filters that match one of your classes. These filters could be used to ignore all problems found in a particular class.
  • Filters that match particular bugcodes in one of your classes. These filters could be used to ignore some bugs found in a particular class.
  • Filters that match a set of bugs. These filters could be used to ignore a set of bugs across all of the analyzed classes.
  • Filters that match particular methods in one of the analyzed classes. These filters could be used to ignore all bugs found in a set of methods for a class.
  • Filters that match some bugs found in methods in one of the analyzed classes. You could use these filters to ignore some of the bugs found in a particularly buggy set of methods.

That's all there is to getting started. See the FindBugs documentation for more details on additional ways the FindBugs task can be customized. Now that we know how to set up a build file, let's take a closer look at integrating FindBugs into your build process.


Integrating FindBugs into your build process

You have several options when it comes to integrating FindBugs into your build process. You can always execute FindBugs from the command line, but more than likely you're already using Ant for your build, so using the FindBugs Ant task is the most natural. Because we've covered the basics of using the FindBugs Ant task earlier, I'll cover some of the reasons you should add FindBugs to your build process and discuss a few of the issues you may run into.

Why should I integrate FindBugs into my build process?

One of the first questions that's often asked is why would I want to add FindBugs into my build process? While there are a host of reasons, the most obvious answer is that you want to make sure problems are detected as soon as your build is run. As your team grows and you inevitably add more junior developers to the project, FindBugs can act as a safety net, detecting identified bug patterns. I want to reiterate some of the sentiment expressed in one of the FindBugs papers. If you put enough developers together, then you're going to have bugs in your code. Tools like FindBugs certainly won't find all the bugs, but they'll help find some of them. Finding some now is better than your customers finding them later -- especially when the cost of incorporating FindBugs into your build process is so low.

Once you've stabilized which filters and classes to include, there's a negligible cost for running FindBugs, with the additional benefit that it detects new bugs. The benefit is probably even greater if you've written application-specific detectors.

Generate meaningful results

It's important to recognize that this cost/benefit analysis is only valid so long as you don't generate a lot of false positives. In other words, the tool's value is diminished if, from build to build, it is no longer simple to determine whether new bugs have been introduced. The more automated your analysis can be, the better. If fixing bugs means having to wade through a lot of irrelevant detected bugs, then you'll likely not use the tool very often, or at least not make good use of it.

Decide which set of problems you don't care about and exclude them from the build. Otherwise, pick a small set of detectors that you do care about and run just those. Another option would be to exclude sets of detectors from individual classes, but not others. FindBugs offers a lot of flexibility with its use of filtering, which should help you generate results that are meaningful to your team, which leads us to the next section.

Determine what you will do with the results of FindBugs

It may seem obvious, but I've worked with more teams than you might imagine who apparently add FindBugs-like tools to their builds for the pure joy of it. Let's explore this question in a bit more detail -- what should you do with your results? It's a difficult question to answer specifically because it has a lot to do with how your team is organized, how you deal with code ownership issues, and so on. However, here are some guidelines:

  • You may want to consider adding the FindBugs results to your source code management (SCM) system. The general rule of thumb is don't put build artifacts into your SCM system. However, in this particular case, breaking the rule may be the right thing to do because it allows you to monitor the quality of the code over time.
  • You may choose to convert the XML results file into an HTML report that you post on your team's Web site. The conversion can be carried out with an XSL stylesheet or script. Check the FindBugs Web site or mailing list for examples (see Resources).
  • Tools like FindBugs can often turn into political weapons used to bludgeon teams or individuals. Try not to encourage that or let it happen -- remember, it's just a tool that's meant to help you improve the quality of your code. With that inspirational aside, in next month's installment I'll show you how to write custom bug detectors.

Summary

I encourage you to try some form of static analysis tool on your code, whether it's FindBugs, PMD, or something else. They're valuable tools that can find real problems, and FindBugs is one of the better ones for eliminating false positives. In addition, its pluggable architecture provides an interesting test bed for writing invaluable application-specific detectors. In Part 2 of this series, I'll show you how to write custom detectors to find application-specific problems.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10944
ArticleTitle=FindBugs, Part 1: Improve the quality of your code
publish-date=05252004