Sunday, December 11, 2016

Eclipse annotation based null analysis



The annotation based null analysis is a tool to add information to the Java type system, which particular references can be null, and which will never be.

Eclipse on its own, can already do flow analysis and show places where the sequential logic will lead to null pointer accesses.

But the annotation based null analysis makes the checking more tightly. This leads to more null checks and therfore to more "fail fast". The errors become obvious right away at those places where the first time a null enters a "shall not be null" field/parameter, not later when it is accessed.

@NonNull // from org.eclipse.jdt.annotation
private Listener listener;

A field marked with @NonNull must be initialized with a valid reference. Either directly at the field declaration or in the constructor. Eclipse will flag missing initialization with an error, even if done from within a method called from the ctor

@Nullable // from org.eclipse.jdt.annotation
private Listener listener;

With the added information, Eclipse can help in more depth in finding errors.

With "Eclipse External Annotation" external libraries can be annotated very easily. This is essential to work with this null analysis.

In the end, the contract between caller and callee is enhanced with the information which parameter and return values can be null. Before, this could only be communicated through documentation.

Preparation


In Eclipse, the project's compiler settings must enable like this.
Project Properties -> Java Compiler -> Warnings/Errors -> 
  • Null analysis: Check "Enable annotation-based null analysis" (a popup will do the next 2 for you)
  • Null analysis: "Null pointer access" : Error
  • Null analysis: "Potential null pointer access" : Error

For the "Eclipse External Annotations" a folder holding this information must be set. In my project we use the location castle.util/eea

Project Properties -> Java Build Path -> Libraries ->
  • JRE -> External Annotations: /castle.util/eea
  • Plugin Dependencies -> External Annotations: /castle.util/eea

@NonNullByDefault

It makes sense to have "everything" to be non null by default and just mark those places where you want to allow nullable.

This is done with the annotation @NonNullByDefault. It can be applied onto a class or a package. The later makes most sense. 
For this, have a package-info.java in all relevant packages, with content like this.

@org.eclipse.jdt.annotation.NonNullByDefault
package castle.util;


Migration

When you work with existing code, the amount of potential errors found might be very big. Most probably you cannot fix all at once.

When you have the project settings stored in version control, you should stay with null analysis disabled in version control, while you work with them enabled locally. Then when all errors are fixed, the project settings can be committed.

Applying Annotations

The following is general about annotation, so you can replace @NonNull and @Nullable.

Sequence with modifiers

@Override
@Nullable
public static Listener getListener(){
    // ...
}


If you use SonarLint it might check for the sequence of modifiers. So while the @Nullable is meant to be on the return value, it shall be placed where you have the annotations for the method.

Arrays

Array are 2 things at once:

private Listener[] listeners;

Here the array itself can be null and/or the elements.

This make the element nullable:

@Nullable
private Listener[] listeners;
private @Nullable Listener[] listeners2; // same as before

To make the array itself nullable:

private Listener @Nullable[] listeners = null; // OK

And finally to have the array and the elements nullable:

@Nullable
private Listener @Nullable[] listeners = null;


Qualified names


If you want to use explicit package names or refer to nested types, the annotation must be applied like this:

private castle.testenv.base.config.@Nullable IExu exu = null; // OK

Also parameters and generics types can have these annotation.


Dealing with external code

You write simple code, it pops up warnings and errors.









Your method has the implicit @NonNull for the return value. But the JRE StringBuilder class is not having annotation information. So Eclipse cannot tell, if this is probably a problem. Hence a warning.

To solve this, you need to have the sources attached to your external code. If you have the JDK installed, this is normally the case.

Now you can make the annotation:

  1. navigate to the method
  2. place cursor onto the parameter or return value'
  3. press Ctrl-1 and take the choice







This creates a file in the previously configured eea location, if not yet existing.
I configured "/castle.util/eea", so it creates the files "/castle.util/eea/java/lang/StringBuilder.eea".

From the source code of this StringBuilder toString() or the documentation, i can make a judgement, if this return type is @NonNull or @Nullable.

If I would choose @Nullable, as a consequence, the warning in my code would turn into an error. Because now Eclipse knows that this is a problem.
If I would choose @NonNull, the warning in my code goes away.

The stored information in the eea file looks like this:

class java/lang/StringBuilder
toString
 ()Ljava/lang/String;
 ()L1java/lang/String;

It identifies the method with its signature and gives the alternative with the null annotation change. A 1 indicates @NonNull, a 0 the @Nullable.
Here you can read it as: the toString method with no argument and a return type of @NonNull String.

Overriding external methods, implementing interfaces


Example overriding equals. Due to @NonNullByDefault, the Object parameter is taken as @NonNull, but this is a conflict of nullable parameter of the external Object equals method.
If you class extends Object, for a foreign caller, it must be possible to call equals() with the same condition as usual. The caller might not know, that this is your class, and that you don't accept null.







To solve this, you need to make this method compatible.








In other cases, it might be clear that the API is not allowing for nullable parameter.
Then you can choose to annotate it as shown above.

Checking for null


Now let's look at code with nullables.

Checking nullable fields


@Nullable
private Path outputPath;

void action(){
    if( outputPath == null ){
        outputPath = getDefault();
    }
    // Potential null pointer access: this expression has a '@Nullable' type
    outputPath.toString();
}

Eclipse gives an error, even if we checked for null. The flow analysis in fact did see, that we assigned the field, but the problem is, that concurrent running code, could modify the field again to be null. 

The save solution is to read the field once into a local variable and write it back when needed.


void action(){
    Path path = outputPath;
    if( path == null ){
        path = getDefault();
        outputPath = path;
    }
    path.toString();
}



Now the flow analysis in Eclipse detects that in the last line, the path variable is always non-null.

Having utilities

I use utility methods. What i found useful for null analysis are these:

General null checking

class Assert {
    public static <T> @NonNull T isNotNull( @Nullable T obj )
}

If the parameter is failing the check, a RuntimeException is thrown, otherwise the value is returned, now as a @NonNull type.

Objects

Utils that can work with null.

class ObjectUtil {
    public static isEqual( Object o1, Object o2 )
    public static <T> T nonNullOrElse(@Nullable T vv, T el) {
    public static <T> T nonNullOrCompute(@Nullable T vv, Supplier<T> supplier)
}

Strings


class StringUtils {
    public String ensureNonNull( @Nullable String str )
    public static boolean hasContent(@Nullable String str)
    public static String toString(@Nullable Object o) {
    public static String hasContentOrElse(String str, String elseValue )
    public static String hasContentOrCompute(String str, Supplier<String> elseValue )
    public static boolean equalsIgnoreCase(@Nullable String str1, @Nullable String str2)
    public static boolean startsWith(@Nullable String string, String prefix)
    // ...
}

How to avoid nullables


For sure it would be all very easy if there would be no null at in the program.
But it is hard, because some things live shorter than our current object and still we need to reference them.

If there are many references to nullable fields, we need many places where we check for non-null, just to make the tool (Eclipse) happy. This leads to frustration.

One of the simple cases is, where we can apply good defaults. 
But you need to verify that the null state of the field is nowhere tested and a decision is made with it.
E.g. you have a "title" string in a dialog, you might be able to initialize it to the empty String and change the field to a nonnull.

The Java8 Optional class and the "Null object pattern" can be used, to turn nullable fields into nonnull ones. But this hides the problem. In fact you would work against the static analysis and turn all accesses into runtime behavior again.

In my opinion, i is the best case, when you can redesign the classes to have only few nullable fields.

As an example i want to show how to make a JFace dialog with only one nullable field.
The problem here is, that the initialization of all the widgets in the dialog is not done in the constructor, it is done in an overridden method called "createDialogArea()". 
The SWT widgets cannot be created upfront, because they require their parents, which do not yet exist be passed into their constructors.
But when i need access to those widget later, i need to have nullable references.

class MyDialog extends TitleAreaDialog {
    @Nullable Button enableCheckBox;
    public void createDialogArea( Composite parent ){
        // ...
        enableCheckBox = new Button( parent, SWT.CHECK );
        // ...
    }
}

With this, i have to check for non-null before all accesses for that widget. I might have many of those, and my code might became very ugly.

class MyDialog extends TitleAreaDialog {
    @Nullable Gui gui;
    public void createDialogArea( Composite parent ){
        gui = new Gui( parent );
    }
    class Gui {
        Button enableCheckBox;
        Gui( Composite parent ){
            // ...
            enableCheckBox = new Button( parent, SWT.CHECK );
            // ...
        }
    }
}

Now the code of the class is split in two parts, the outer and that in the inner. The inner class can have all the references as nonnull. I can register listeners calling into the Gui class and use other fields without checks.
When i override other methods in the main class, i need to forward to the Gui with a single null check only.