Showing posts with label eclipsecon. Show all posts
Showing posts with label eclipsecon. Show all posts

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!

Monday, November 14, 2011

Of Compliance and Source settings in Eclipse java compiler


Going through a few stack overflow posts and a few bugs, I realized that there's still a lot of confusion about the  source and compliance level settings available in JDT. Well, frankly, almost everyone I know was confused when they started using JDT, even the current JDT committers themselves!
These settings are available in Preferences>Java>Compiler, or in your project's .settings folder>org.eclipse.jdt.core.prefs file.



So, what do the settings imply and what do they do?

Source compatibility:
This setting should be used to select upto which java version the user intends to use the language features. Eg.: setting it to 1.7 means that the user can use strings in switch, the diamond construct, etc. but setting it to 1.6 will mean that the user can not use such 1.7-only language features.
For the JDT compiler itself, we use a compliance of 1.4. Yes, quite old-fashioned you'd say, but this was a conscious choice to enable people to use the JDT compiler even if they dont have a version of java higher than 1.4 installed on their machines. You can also chose a source setting for your project on such considerations.

Compiler Compliance level:
This setting is a kind of a switch to make the JDT compiler "compliant" to a certain Oracle javac version. This is because not all bugs fixed in the Oracle javac compiler get backported to earlier releases. So, there are many differences between each javac version. There are even cases when only one javac version differs from the others, before and after it. So, we provided the compliance setting for users to replicate that difference in behaviour in the JDT compiler. This is to enable people who compile with ECJ and ship with javac or vice-versa, to see consistent errors (or the lack thereof).

Take, for example, the following case:

package com.example;

import java.util.List;


public class ErasureBug {


    public String output(List<Integer> integers) {


        return "";
    }


    public void output(List<Double> doubles) {


    }
}


This code compiles without error only in javac 6, but fails with a duplicate method erasure in both javac 5 and javac 7. 


So, in Eclipse when the user sets the project "compliance" to 1.6, he/she will be able to compile the code as shown, while in 1.5 and 1.7 modes, the code will fail.




Generated .class files compatibility:
This setting changes the generated bytecode to be compatible to a certain VM version. This is required at runtime when the VM available is lower than the one for which the code is compiled. So, if you set source level at 1.3 , use 1.3 features your code and expect your clients to still be able to use the older 1.2 or lesser VM to execute your code, you should set the .class file compatibility setting to 1.2 or less. This does not work for higher java versions, because suppose you use annotations in your code which only came in 1.5, or instructions such as invokedynamic in 1.7, which introduced a new Bootstrap method attribute, earlier VM's may still not be able to execute the class files. So we cannot guarantee backward compatibility and the UI thus will not allow you to set class file compatibility lower than the source level. However, if you're using features such as generics in 1.5 source level, it's quite safe for a 1.4 VM to execute those. So, you can manually set the org.eclipse.jdt.core.compiler.codegen.targetPlatform property to 1.4 in project>settings folder>org.eclipse.jdt.core.prefs file.


Note that the source level cannot be more than the compliance level.


Hope this gives more clarity on the settings.


EclipseCon update:
I have the following submissions for EclipseCon 2012. Do vote if you find them interesting: