Checker Nullness   as of Julia version 2.5.0 (built on 4 Jul 2018)

belongs to group Advanced

Identify where null might be dereferenced or passed to a library


This checker looks for program points where, at runtime, a null pointer might be dereferenced and the execution stopped with a NullPointerException. It also looks for calls to library methods where null, or a collection or array containing a null element, might be passed as a parameter in a place where this is not allowed.

Action: check if the value is actually null and modify the program in such a way to be sure that it is never null again. In most cases, a nullness bug is the consequence of a more complex bug, where some field is not always initialized before being used. Try to define as many as possible of your fields as final. Do not use null as a special value returned by a method.

Since nullness is undecidable, it is impossible to find all and only the nullness bugs, without any false positive. As a consequence, Julia will often issue spurious warnings, where a fake bug is signalled because the analyzer is not clever enough to understand that some value in never actually null. However, Julia is currently the only analyzer that does not miss nullness bugs: if there is a real bug, it will be signalled. If the number of false alarms is too high and soundness is not paramount, it is also possible to use the faster BasicNullness checker, which is unsound but yields its results much more quickly and with fewer false alarms than Nullness.

Manually-provided Annotations

In order to reduce the number of false positives, you can instruct the analyzer with nullness annotations, placed in the code that you analyze. Usually, one performs a first nullness analysis and gets many fake warnings (false positives), but most of them have the same origin: a field that Julia does not identify as non-null or a method that Julia does not identify as always returning a non-null value. Hence, you can use this first analysis to place annotations in your code and then start the analysis again. In particular, Julia can be helped by putting the following annotations in the code under analysis:

  • @NonNull for fields and return values: states that the field is never null when it is read, or that the method always returns a non-null value;
  • @Inner0NonNull for fields and return values of generic type: states that the first generic type variable of the values held in the field or returned by the method is non-null. For instance, this allows one to state that a set bound to a field contains non-null elements only or that a method returns a map whose keys are all non-null;
  • @Inner1NonNull for fields and return values of generic type: as in the case above, but predicates over the second generic type variable. For instance, this allows one to state that the values of a map held in a field are all non-null.
  • @Nullable for the formal parameters of a method or constructor in a library: it states that it is allowed to pass null for that parameter.

It is also possible to state that a given expression is non-null at a specific program point. For that, use NullnessAssertions.assertNonNull(expression). Note that the fact that a variable is non-null at a program point does not prevent it from holding null later.

Jaif Annotation File

If the jaif option of this checker is enabled, a jaif file is generated. It is a text file that contains nullness annotations for fields, constructor and method parameters and the return value of the methods. In this sense, it is a compact representation of the information inferred by this checker. The default annotation is @NonNull, hence the latter is never explicitly reported in the file.


Examples


Consider the following fragment of code, taken from the BluetoothChat sample application by Google, distributed with its ADT:

  // Read from the InputStream
  int bytes = this.mmInStream.read(buffer);

This checker issues the following warning (the exact line depends on the version of the sample application):

com/example/android/BluetoothChat/BluetoothChatService.java:2: [Nullness: FieldNullWarning] is the value of field mmInStream non-null?

This means that, according to Julia, it seems possible that field mmInStream might hold null there and a runtime NullPointerException might be thrown. A similar warning is signalled about field mmOutStream as well. Those fields are initialized inside the constructor:

  private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket, String socketType) {
      Log.d(TAG, "create ConnectedThread: " + socketType);
      mmSocket = socket;
      InputStream tmpIn = null;
      OutputStream tmpOut = null;

      // Get the BluetoothSocket input and output streams
      try {
        tmpIn = socket.getInputStream();
        tmpOut = socket.getOutputStream();
      } catch (IOException e) {
        Log.e(TAG, "temp sockets not created", e);
      }

      mmInStream = tmpIn;
      mmOutStream = tmpOut;
    }
  }

However, if the input or output streams of the bluetooth socket cannot be opened (for instance, if the mobile device has no bluetooth hardware interface or has that interface turned off or broken), an IOException is thrown at line 14, variables tmpIn and tmpOut keep their initial value null from lines 9 and 10 and fields mmInStream and mmOutStream get assigned null at lines 20 and 21. Hence, Julia is right: there might be a NullPointerException at BluetoothChatService.java:2 because those two fields might actually hold null.

To fix that bug, when the streams cannot be opened, the construction of the object should abort with an exception:

 private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket, String socketType) throws IOException {
      Log.d(TAG, "create ConnectedThread: " + socketType);
      mmSocket = socket;
      InputStream tmpIn = null;
      OutputStream tmpOut = null;

      // Get the BluetoothSocket input and output streams
      try {
        tmpIn = socket.getInputStream();
        tmpOut = socket.getOutputStream();
      } catch (IOException e) {
        Log.e(TAG, "temp sockets not created", e);
        throw e;
      }

      mmInStream = tmpIn;
      mmOutStream = tmpOut;
    }
  }

With these changes, Julia does not issue anymore a warning about the possible nullness of fields mmInStream and mmOutStream, since it can now prove them non-null.

Consider now the following program:

import java.util.Set;
import java.util.HashSet;

public class Nullness {
  public static void main(String[] args) {
    Set<Object> set = new HashSet<>();
    Set<String> set2 = new HashSet<>();

    for (String s: args)
      set2.add(s);

    if (System.currentTimeMillis() % 2 == 0)
      set2.add(null);

    set.add("hello");
    set.add(new Object());

    for (Object o: set)
      o.toString();

    for (String s: set2)
      s.toString();

    for (String s: set2) {
      System.out.println(s);
      System.getProperty(s);
    }
  }
}

The HashSet set contains non-null elements only, while the HashSet set2 might contain null among its elements, because of line 13. Julia understands this fact and this checker issues the following warnings:

Nullness.java:22: [Nullness: CallOnNullWarning] is the receiver of "toString" non-null?
Nullness.java:26: [Nullness: ActualNullWarning] is parameter "key" of "getProperty" non-null?

That is, Julia finds that method toString() might be called on a null receiver at line 22, which would raise a NullPointerException. Moreover, null might be passed as actual parameter to the getProperty() method at line 26, which is illegal and would raise a NullPointerException as well. It is interesting to observe that Julia does not issue any warning about the fact that null might be passed as actual parameter to the println() method at line 25. This is because that method allows null as its argument, in which case it prints the string "null" and does not throw any exception.