Tuesday, December 6, 2011

Inter-procedural Null analysis using annotations in Eclipse JDT

The JDT team is extremely excited with the release of the new annotation based null analysis. Before writing anything about it I want to thank Stephan Herrmann (JDT committer from Germany) for all the hard work he has put in on this feature! Feel free to drop him a word of appreciation on stephan AT cs.tu-berlin.de

What is JDT's annotation based null analysis?
To give a bit of a background, you must've noticed that JDT java compiler does some static analysis and based on that warns if you deference a variable that has been analysed to be (potentially) null at runtime, or compare a variable against null when its nullness at runtime can be determined at compile time.

[See line numbers to locate error messages]
However, the warnings are only generated for local variables inside a method. There's no way to find out the change of nullness due to a method call, or for parameters of a method. Consider the example on the left. o1 is a parameter and since we don't know how foo(..) will be called and what value will be passed to o1, we can't do any analysis on it unless its state definitely changes inside foo(..). Also note that even though o1 is assigned return value of method  bar(), which returns null, the compiler cannot figure that out during static analysis and assumes the return value of bar() to be an unknown value.

Null annotations offer a solution here. They are a way of enforcing null contracts in your code to make sure you catch such (potential) NPE's before they occur at runtime and become a stop-ship bug for your product.

How do I use null annotations?
JDT now comes bundled with a JAR (org.eclipse.jdt.annotation) containing null annotations. The types of annotations offered are:
NonNull annotation
Default is @org.eclipse.jdt.annotation.NonNull -
  • when this annotation occurs on a return type, it enforces a non null return value for a method.
  • when this annotation occurs on a method parameter, it enforces a non null value to be passed to the method call and that only a non null value can be assigned to this parameter inside the method.

Nullable annotation
Default is @org.eclipse.jdt.annotation.Nullable-
  • when this annotation occurs on a method return type or parameter, it says that the returned value can be null, and so can be the argument.

NonNullByDefault annotation
Default is @org.eclipse.jdt.annotation.NonNullByDefault-

  • This can be applied to a package (in package-info.java), class or method to enforce nullability for all enclosed elements. The annotation alone enforces non nullability of enclosed method return value and parameter values.
  • If applied with a an argument i.e. NonNullBeDefault (false), it will cancel out any global default for the particular element.

To use these annotations in your project, right click Project>Build Path>Add External Archives and browse to org.eclipse.jdt.annotation JAR in the "plugins" directory of your eclipse install location. (We are working on the UI for this.)

One can also use their own annotation types as null annotations. Just make sure they are on the project's buildpath.

To enable null annotation based analysis and specify the annotation names, navigate to Preferences>Java>Compiler>Errors/Warning>Null analysis.

(Note also the "use non-null as default" option, which you can use to set a universal (workspace/project level) default to be applied to all method return types and parameters.)

What new errors/warnings should I expect to see with null annotations enabled?
As seen in the above dialog, there are two kind of diagnostics of primary importance - violation of null specification and potential violation of null specification. In addition to these the existing null analysis gets enhanced, and now for annotated method parameters a null state is available inside a method.

Violation of null spec is issued in cases when:
1. You pass a null value as an argument to a method whose corresponding parameter is annotated with @NonNull
2. You assign a @NonNull annotated parameter a null value.
3. You return a null value in a method that is annotated with @NonNull
4. You inherit a method annotated with @NonNull but now loosen the contract by annotating child method as @Nullable.
5. You inherit a method with a parameter annotated with @Null but now tighten the contract by annotating child method's corresponding parameter as @NonNull.

[See line numbers to locate error messages]

Potential violation of null spec is issued when:
1. You pass a value evaluated to be null on some execution path as an argument to a method whose corresponding parameter is annotated with @NonNull
2. You assign a @NonNull annotated parameter a value ascertained to be null on some execution path.
3. You return a possibly null value in a method that is annotated with @NonNull.

[See line numbers to locate error messages]

Insufficient null info is issued when there's not sufficient information to evaluate a given value to be (possibly) null and that value is passed as an argument, assigned to a parameter or returned from a method.

[See line numbers to locate error messages]

Redundant null annotation is issued when an annotation has the same effect as one applied already in an enclosing element.

[See line numbers to locate error messages]

Note that this is a work in progress and we will continue to polish the same for the Juno release. We're also working on ways to help people use this functionality easily (eg.: Support for nullity profiles for libraries and Quick fixes for null annotations. ) .
For a sneak preview of this feature, drop in at the EclipseCon 2012 tutorial - How To Train the JDT Dragon. If you find any issues while testing this feature, please open a bug. I hope you're as excited as we are about this cool new feature!