Introduction

However, the current analysis is restricted to flow analysis within one method.
No assumptions can be made about

arguments flowing into a method

return values from method calls and

field reads.

In order to include these elements in the analysis one could either

use whole program analysis (very expensive - not feasible for a (incremental) compiler)

explicit contracts via an extended type system or annotations

The second option is well explored in research and some existing tools (like the Checker Framework, JML, FindBugs)
already introduce specific annotations to this end.

One could argue that advanced analysis should be left to specialized tools
but having something like this in the JDT compiler should show two benefits:

feedback is more immediate and it is available for all JDT users without installing more software

analysis might be more precise than existing tools, because the actual flow analysis in the JDT compiler is already pretty strong (unproven claim).

A preparatory discussion of the design space can be found here: /Brainstorming.

Actual Strategy in the JDT

Disclaimer: this is work in progress. No promise is made that this particular feature will be part of any particular release of the JDT.

By default the JDT does not support inter-procedural null analysis, however, a prototype exists allowing the JDT to be configured to use annotations for null contracts across method boundaries. The prototypical implementation is currently
based on the OT/Equinox technology for better maintainability at this stage. This particular prototype is known to have negative impact on the compiler performance, which is however no indication about how the final in-place implementation will perform.

Installing the prototype

Get an Eclipse SDK ≥ 3.7M5

A build ≥ 20110226 is required when using null annotations in package-info.java

Some of the operations below (multi quickfixes) require lots of memory, you may want to add s.t. like -Xmx800m already now

Cannot tighten null contract for parameter p, inherited method from T does not constrain this parameter.

The second form occurs when no null default applies at the scope of the super method.

The fix is:

Adjust overridden method from T, mark parameter as @NonNull

These quickfixes can be applied...

individually (Ctrl-1)

all occurrences per file (via the hover)

all occurrences (via context menu in the Problems view)

Note, that some quickfixes require to modify another compilation unit (file) than the one
where the problem was observed. For these quickfixes the current implementation doesn't
support fixing several equal issues in bulk (for the technical background see
bug 337977).

Compiler configuration explained

By default the JDT does not recognize any null annotations but it can be configured to do so.
For this purpose these options are proposed:

Previous versions of this proposal contained emulation and default import of annotation types, which may, however, not be supported by the final solution. See older versions of this page for details (≤ 20110226).

Use this if your code has a bias to either @NonNull or @Nullable. By defining a global default you save the effort of adding annotations to the majority of locations.

There is no built-in default for this option. The option name is

org.eclipse.jdt.core.compiler.annotation.nulldefault

It is the user's responsibility to make the required annotation types available on the build path.

Null Contracts

Once properly configured the JDT compiler supports specification and checking of null contracts. Each part of a null contract implies an obligation for one side and a guarantee for the other side.

Method Parameters

When a method parameter is specified as nullable this defines the obligation for the method implementation to cope with null values without throwing NPE. Clients of such a method enjoy the guarantee that it is safe to call the method with a null value for the given parameter.

When a method parameter is specified as nonnull all clients are obliged to ensure that null will never be passed as the value for this parameter. Thus the method implementation may rely on the guarantee that null will never occur as the value for this parameter.

Method Returns

The situation is reversed for method returns. All four cases are summarized by the following table:

caller

method implementation

nullabel parameter

may safely pass null without checking

must check before dereference

nonnull parameter

must not pass null

may use without checks

nullable return

must check before dereference

can safely pass null

nonnull return

may use without check

must not return null

Local Variables

Null contracts can also be defined for local variables although this doesn't improve the
analysis by the compiler, because local variables can be fully analyzed without annotations, too. Here the main advantage of null annotations is in documenting intentions.

The following is an example of a program where all sides adhere to their respective part of the contract:

Althoug we know that toUpperCase() will never return null, the compiler does not know as long as java.lang.String does not specify null contracts. Therefor the compiler has to raise a warning that it has "insufficient nullness information" to guarantee contract adherence.

Null annotations for the local variables are redundant here, as the nullness information can be fully derived from the variable's initialization (and no further assignments exist).

Null Contract Inheritance

A method that overrides or implements a corresponding method of a super type (class or interface) inherits the full null contract. Its implementation will thus be checked against
this inherited contract. For the sake of safe polymorphic calls,
null contracts must be inherited without modification, or be redefined in the following ways:

A nonnull method parameter may be relaxed to a nullable parameter. The additional checks have to be performed in the body of the overriding method. Callers of the super type must still pass nonnull, while callers which are aware of the sub type may pass null.

A nullable method return (or a return with no null annotation) may be tightened to a nonnull return. The additional checks must again be performed in the body of the overriding methods. Callers of the super type still have to check for null, whereas callers which are aware of the sub type may safely assume nonnull return values.

Any overrides that attempt to change a null contract in the opposite directions will raise a compile time error.

This explicitly implies that callers only need to inspect the null contract of the statically declared type of a call target to safely assume that all runtime call targets will adhere (at least) to the contract as specified in the statically declared type, even if the runtime type of the call target is any sub type of the declared type.

Defaults at different levels

If no null annotations are used, the compiler uses the original Java semantics,
where the following is legal for all variables of reference types:

assign null, and

dereference without check.

The above mentioned preference (org.eclipse.jdt.core.compiler.annotation.nulldefault) allows to globally change this so that any declaration
to which no null annotation applies (directly or via inheritance) will be considered
as either nullable or nonnull, depending on that specific setting.

For more fine-grained control two additional annotations can be used. The qualified type
names of these annotations can be configured using these preferences:

The built-in values for these preferences are org.eclipse.jdt.annotation.NullableByDefault and org.eclipse.jdt.annotation.NonNullByDefault.

These annotations can be applied to any Java type or package and affect all
method returns and parameters with undefined null status within their scope.

A default declared at an outer scope can be overridden by a different default at an
inner scope. However, no means are provided for canceling a default (as to re-establish
the original Java semantics).

Future

The following bugzillas address future improvements of the above strategy (order roughly by priority):