Skip to main content

Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "JDT Core/Null Analysis"

(Null Contract Inheritance)
Line 19: Line 19:
 
* explicit contracts via an extended type system or annotations
 
* explicit contracts via an extended type system or annotations
  
The second option is well explored in research and some existing tools (like FindBugs)
+
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.
 
already introduce specific annotations to this end.
  
One could argue that advanced analysis should be left to specialized tools (like FindBugs)
+
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:
 
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
 
* 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).
+
* analysis might be more precise than existing tools, because the actual flow analysis in the JDT compiler is already pretty strong (unproven claim).
  
==Design Space for Inter-Procedural Null Analysis==
+
A preparatory discussion of the design space can be found here: [[/Brainstorming]].
This section discusses different options how tools for inter-procedural analysis could be designed. This write-up was made in preparation of designing a concrete strategy for the JDT.
+
  
===Degree of Annotating===
 
A '''radical approach''' would suggest that '''every reference type''' in the program must explicitly exclude or include the value <code>null</code>. E.g., <code>String</code> would not be legal type in any declaration (local variable, field, method signature) but only <code>@NonNull String</code> and <code>@MaybeNull String</code> are.
 
  
This radical approach has two problems:
+
==Actual Strategy in the JDT==
* it introduces vast efforts for annotating every type reference in the program
+
* it is very difficult to apply to intermediate variables within a method body with branches, loops etc.
+
E.g., the JDT compiler has no problem seeing that this is safe:
+
<source lang="java">
+
Foo foo2 = null;
+
if ((foo != null) && foo.isOK())
+
  foo2 = foo;
+
else
+
  foo2 = new Foo();
+
foo2.bar();
+
</source>
+
In the radical approach at least two more variable were necessary: at each point where analysis finds that a value cannot be null, a new variable with a differently annotated type would be needed. By contrast, the compiler can manage these intermediate states implicitly.
+
  
Thus it seems better feasible not to strive for full proofs of the absence of runtime errors, but to focus on gradually feeding more information into the analysis in order to just detect ''more'' (instead of all) potential runtime errors already during compilation. The radical approach can be weakened in two ways:
+
'''Disclaimer''': this is work in progress. No promise is made that this particular feature will be part of any particular release of the JDT.
* make annotations optional
+
* limit the program locations where annotations should occur (note, how this actually relates to [http://jcp.org/en/jsr/summary?id=308 JSR 308]).
+
  
In the vein of design by contract the following locations are relevant
+
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
* method parameters (= method precondition)
+
based on the [[:Category:OTEquinox|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.
* method return value (= method postcondition)
+
* fields (= invariant)
+
To truly reflect design by contract one might want to weaken the rules to
+
* exclude non-API methods from contracts
+
*: '''but''' ''inter-procedural analysis is also relevant within a class, so annotating even private methods is useful, too''.
+
* accept inconsistent field states in the middle of a method body (only the first read and the last write within each method would be checked)
+
*: '''but''' ''such intermediate inconsistent states might be observed by concurrent executions and would make the analysis unreliable.''
+
  
===Syntax===
+
===Installing the prototype===
Generally annotations could happen in two ways:
+
  
Extended '''Javadoc''', like:
+
* Get an Eclipse SDK &ge; 3.7M5
<source lang="java">
+
* Enter this update URL:
/**
+
** http://download.eclipse.org/objectteams/updates/contrib
* @param @nonnull input ...
+
* Select and install these features:
* @return @maybenull ...
+
** JDT Null Annotation Checker (Incubation)
*/
+
** Object Teams Equinox Integration (Incubation)
public String foo(String input) { ... }
+
</source>
+
  
Alternatively, '''Java 5 annotations''' can be used to say the same:
+
The code is hosted at svn://dev.eclipse.org/svnroot/tools/org.eclipse.objectteams/trunk/plugins/org.eclipse.objectteams.jdt.nullity
<source lang="java">
+
public @maybenull String foo(@nonnull String input) { ... }
+
</source>
+
  
This is where [http://www.jcp.org/en/jsr/detail?id=305 JSR 305] comes into focus which covers the issue of standardizing "Annotations for Software Defect Detection"
+
===Example usage===
 +
In order to try the prototype against any existing Java project the following steps should help (I tried it using the JDT/Core as an example):
  
After a first analysis I see two problems with JSR 305:
+
* Prepare the following compiler configuration
* It has stalled, no official documents produced in 4 years, I couldn't find a proof of any activity during the last 2 years. JSR is marked as inactive, may soon be withdrawn.
+
** enable compliance 1.5 or higher
* It is far more generic than what we need for this specific issue: everything is built upon a meta annotation <code>@TypeQualifier</code>, it invests in supporting four states:
+
** enable all null-related warnings
** unspecified (no annotation)
+
** disable warnings regarding generics if the project doesn't use generics (don't spam the problems view)
** @UnknownNullness (same interpretation as unspecified)
+
** @Nonnull
+
** @NullFeasible
+
: The rationale for @UnknownNullness is for discarding an inherited specification. I wasn't convinced that any contract could be specialized to "no contract" - when specializing an inherited contract you should be explicit what the new contract is (which must be conform to the inherited contract).
+
See also these [http://www.cs.umd.edu/~pugh/JSR-305.pdf slides (May 2008)] by William Pugh.
+
  
The main issue with both syntaxes is the lack of standardization.
+
* Since there isn't yet any UI for the new compiler preferences the following lines should be added manually into <code>.settings/org.eclipse.jdt.core.prefs</code>
 +
org.eclipse.jdt.core.compiler.annotation.nullable=annotations.Nullable
 +
org.eclipse.jdt.core.compiler.annotation.nonnull=annotations.NonNull
 +
org.eclipse.jdt.core.compiler.annotation.nulldefault=nonnull
 +
:* the first two options specify the fully qualified names of those annotation types to be used for null contracts.
 +
:* the third option tells the compiler that any types that have no explicit null contract should be assumed as non-null (applies to method parameters and method return type).
  
====Retention====
+
* Create the annotation types specified above like this (in their correct package):
Annotations have the advantage that a CLASS retention (or perhaps even RUNTIME) would support compiling against contracts in class files, which is not easily possible with the Javadoc based approach.
+
<source lang="java">
 
+
@Retention(RetentionPolicy.CLASS) public @interface Nullable { }
===Standard vs. Configuration===
+
@Retention(RetentionPolicy.CLASS) public @interface NonNull { }
Once the JDT compiler officially supports any specific syntax this creates a de-facto
+
</source>
standard which might conflict with existing and future standards (any code written against
+
the de-facto standard might be incompatible with future tools).
+
 
+
Possible solutions:
+
# Wait for the standard annotations (which may be waiting for ever)
+
# Make the concrete syntax configurable
+
#* select between Javadoc and annotation styles
+
#* select the exact annotation classes to use (cf. {{bug|186342#c12}}).
+
#: Do ''not'' provide a default, as not to define anything standardish
+
# Provide implementation as a separate "use on your own risk" plug-in
+
 
+
(1) doesn's look attractive to me. (2) is kind-of a workaround, carefully giving the message: we are not defining a standard, if you use this you should be prepared to change your annotations once a standard is created (automatic migration to a new standard shouldn't be so hard OTOH). (3) might be used to side-step the whole issue by saying: we're only doing technical exploration, but interested folks may still download and use this as early adopters. Technically, I would implement this using [[:Category:OTEquinox|OT/Equinox]] :).
+
This would be an intermediate solution (still need to download additional stuff - but works as integral part of the incremental compiler) - shouldn't be difficult to migrate into the JDT/Core once we know more about standardization.
+
 
+
My irrational hopes are, that once users find out how great this is, someone will step forward and declare a standard. It would be great if people have something to play with and actually see the difference.
+
 
+
===Semantical details===
+
I see these issues worth discussing:
+
# Do we need more than @Nonnull and @MaybeNull?
+
# How exactly do annotations interact with inheritance (should mainly just apply the rules of design by contract, actually)
+
# What are the defaults?
+
# How do field specifications interact with concurrency?
+
  
Regarding (3) I believe in [http://users.encs.concordia.ca/~chalin/papers/TR-2006-003.v3s-pub.pdf "Non-null References by Default in Java: Alleviating the Nullity Annotation Burden"].
+
* Build (Project > Clean)
OTOH, for perfect freedom one could make the default configurable in a hierarchical way
+
** At this point you should see plenty of new errors and warnings
(see also [http://www.cs.umd.edu/~pugh/JSR-305.pdf Pugh pp. 43 ff]):
+
* per project, package, class, method
+
* all public, protected, default members
+
* all fields / method parameters / method return values (also for convenience: signatures (param|return) / all)
+
  
{{bug|186342#c14}} points out that finding a good default is difficult because different not-annotated '''library functions''' have different contracts (e.g: HashMap.get() -> @MaybeNull, JTable.getSelectedColumns() -> @NonNull). Thus either default would produce a lot of false positives. For solving this issue one might use more fine grained control also over 3rd-party code, like a '''nullity-profile''', with externalized defaults and exceptions / contracts.
+
====Cleaning up====
 +
The sheer number of new problems may look intimidating but that's where quickfixes will come to the rescue.
 +
Currently the following problems offer a quickfix:
  
Actually, a nullity-profile could also be used to map existing annotations within 3rd-party code to a different annotation class.
+
* Null contract violation: returning null from a method declared as @NonNull
 +
*: Note that the mentioned @NonNull declaration is implicit via the global default
 +
* Null contract violation: return value can be null but method is declared as @NonNull
 +
*: Use with care: the compiler has no clear indication if @Nullable was actually intended or not
 +
* Redundant null check: The variable foo cannot be null at this location
 +
*: Only those occurrences that concern a method parameter
 +
* Null comparison always yields false: The variable bar cannot be null at this location
 +
*: Only those occurrences that concern a method parameter
  
==Actual Stragegy in the JDT==
+
These quickfixes can be applied...
 +
* individually (Ctrl-1)
 +
* all occurrences per file (via the hover)
 +
* all occurrences (via context menu in the Problems view)
  
By default the JDT does not support inter-procedural null analysis, however, starting with {{bug|186342}} the JDT can be configured to use annotations for null contracts across method boundaries.
 
  
===Configuring Null Annotations for the JDT===
+
===Compiler configuration explained===
 
By default the JDT does not recognize any null annotations but it can be configured to do so.
 
By default the JDT does not recognize any null annotations but it can be configured to do so.
For this purpose three independent options exist:
+
For this purpose three independent options are proposed:
 
# Specify the '''names of annotation types''' to be used for marking nullable vs. nonnull types.
 
# Specify the '''names of annotation types''' to be used for marking nullable vs. nonnull types.
#* ''use this if you want to achieve compatibility with annotations defined by some other tool (or a future standard, should one be defined eventually).''
+
#: ''use this if you want to achieve compatibility with annotations defined by some other tool (or a future standard, should one be defined eventually).''
 +
#: [[Image:Ok_green.gif]] '''Implemented'''
 
# '''Emulate''' null annotation types that do not exist on the build path
 
# '''Emulate''' null annotation types that do not exist on the build path
#* ''use this if you don't have any actual null annotation types or if you just want to avoid having to configure your build path for null annotations.''
+
#: ''use this if you don't have any actual null annotation types or if you just want to avoid having to configure your build path for null annotations.''
 +
#: [[Image:progress.gif]] '''Partially implemented''' in the prototype: the Java Model and DOM do not support emulated annotation types and thus the UI will cause some exceptions in this mode.
 
# '''Implicitly import''' null annotation types in every Java file
 
# '''Implicitly import''' null annotation types in every Java file
#* ''use this if you want to use null annotations by their simple name even without a corresponding import statement in your sources.''
+
#: ''use this if you want to use null annotations by their simple name even without a corresponding import statement in your sources.''
 +
#: [[Image:Error.gif]] '''NOT implemented'''
  
 
If at least one of these options is defined / enabled, the compiler will start analysing null contracts. If "emulate" and "implicitly import" are enabled, no further preparations are required for using null annotations in your code.
 
If at least one of these options is defined / enabled, the compiler will start analysing null contracts. If "emulate" and "implicitly import" are enabled, no further preparations are required for using null annotations in your code.

Revision as of 15:20, 30 January 2011

This page discusses a proposed improvement in the static null analysis of the JDT compiler.

See also bug 186342.


Introduction

The static analysis of the JDT compiler detects many potential programming problems related to the null-ness of variables: dereferencing a null value (-> NPE), redundant null checks etc.

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

The code is hosted at svn://dev.eclipse.org/svnroot/tools/org.eclipse.objectteams/trunk/plugins/org.eclipse.objectteams.jdt.nullity

Example usage

In order to try the prototype against any existing Java project the following steps should help (I tried it using the JDT/Core as an example):

  • Prepare the following compiler configuration
    • enable compliance 1.5 or higher
    • enable all null-related warnings
    • disable warnings regarding generics if the project doesn't use generics (don't spam the problems view)
  • Since there isn't yet any UI for the new compiler preferences the following lines should be added manually into .settings/org.eclipse.jdt.core.prefs
org.eclipse.jdt.core.compiler.annotation.nullable=annotations.Nullable
org.eclipse.jdt.core.compiler.annotation.nonnull=annotations.NonNull
org.eclipse.jdt.core.compiler.annotation.nulldefault=nonnull
  • the first two options specify the fully qualified names of those annotation types to be used for null contracts.
  • the third option tells the compiler that any types that have no explicit null contract should be assumed as non-null (applies to method parameters and method return type).
  • Create the annotation types specified above like this (in their correct package):
@Retention(RetentionPolicy.CLASS) public @interface Nullable { }
@Retention(RetentionPolicy.CLASS) public @interface NonNull { }
  • Build (Project > Clean)
    • At this point you should see plenty of new errors and warnings

Cleaning up

The sheer number of new problems may look intimidating but that's where quickfixes will come to the rescue. Currently the following problems offer a quickfix:

  • Null contract violation: returning null from a method declared as @NonNull
    Note that the mentioned @NonNull declaration is implicit via the global default
  • Null contract violation: return value can be null but method is declared as @NonNull
    Use with care: the compiler has no clear indication if @Nullable was actually intended or not
  • Redundant null check: The variable foo cannot be null at this location
    Only those occurrences that concern a method parameter
  • Null comparison always yields false: The variable bar cannot be null at this location
    Only those occurrences that concern a method parameter

These quickfixes can be applied...

  • individually (Ctrl-1)
  • all occurrences per file (via the hover)
  • all occurrences (via context menu in the Problems view)


Compiler configuration explained

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

  1. Specify the names of annotation types to be used for marking nullable vs. nonnull types.
    use this if you want to achieve compatibility with annotations defined by some other tool (or a future standard, should one be defined eventually).
    Ok green.gif Implemented
  2. Emulate null annotation types that do not exist on the build path
    use this if you don't have any actual null annotation types or if you just want to avoid having to configure your build path for null annotations.
    Progress.gif Partially implemented in the prototype: the Java Model and DOM do not support emulated annotation types and thus the UI will cause some exceptions in this mode.
  3. Implicitly import null annotation types in every Java file
    use this if you want to use null annotations by their simple name even without a corresponding import statement in your sources.
    Error.gif NOT implemented

If at least one of these options is defined / enabled, the compiler will start analysing null contracts. If "emulate" and "implicitly import" are enabled, no further preparations are required for using null annotations in your code.

Conversely, if null contracts are enabled ..

  1. ... without specifying annotation type names
    → the following built-in defaults will be used:
    • nullable = org.eclipse.jdt.annotation.Nullable
    • nonnull = org.eclipse.jdt.annotation.NonNull
  2. ... without enabling annotation type emulation
    → the specified annotation types have to be provided on the build path (either as source or binary files).
  3. ... without enabling implicit annotation type imports
    → annotation types have to be imported or referenced by their fully qualified name.

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:

public class Supplier {
    // this method requires much but delivers little
    @Nullable String weakService (@NonNull String input, boolean selector) {
        if (selector)
            return input;
        else
            return null;
    }
    // this method requires nothing and delivers value
    @NonNull String strongService (@Nullable String input) {
        if (input == null)
           return "";
        else
           return input.toUpperCase();
    }
}
public class Client {
    void main(boolean selector) {
        Supplier supplier = new Supplier();
        @Nullable String value = supplier.weakService("OK", selector);
        @NonNull String result = supplier.strongService(value);
        System.out.println(result.toLowerCase());
    }
}

Notes:

  • 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.

Future

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

  • bug 334455 - UI for new preferences regarding null annotations
  • bug 334457 - [compiler][null] check compatibility of inherited null contracts
  • bug 331647 - [compiler][null] support flexible default mechanism for null-annotations
  • bug 331651 - [compiler][null] Support nullity profiles for libraries
  • bug 331649 - [compiler][null] consider null annotations for fields

Back to the top