Null analysis in eclipse compatibility break between 7 and 8 - java

I have encountered strange behaviour of nullcheck analysis under Spring tool suite 3.6.2 (Eclipse clon) on windows 7 64bit with Oracle java 8u25. The same maven project with java 7 source compatibility successfully finds NPE error in eclipse, but when I change compilation version in maven to java 1.8, the eclipse is unable to find this error.
My nullcheck analysis configuration in Eclipse (Java->Compiler->Errors/Warnings->Null analysis) is:
Include asserts in null analysis true
Enable annotation based analysis true
NotNull custom annotation is properly set to javax.validation.constraints.NotNull
etc. (everything seems to be OK, as it works under java 7)
My maven pom is here http://pastebin.com/pF1yJSG2 , as mentioned above when java.version in pom is 1.7 null check works, when is 1.8 null check does not work.
Sample source code is:
package test.nullcheckbug.core;
import javax.validation.constraints.NotNull;
public class Program {
/**
* This will fail to compile under java 7 (null analysis works in STS
* 3.6.2). But under java 8 it does not show error in Eclipse Markers -
* static analysis does not work ?!
*
* #return null which is not allowed
*/
#NotNull
public String fail() {
return null;
}
/**
* Simple main.
*
* #param args
* ignored args
*/
public static void main(String[] args) {
}
}
Does anybody know where is problem and how to enable nullcheck to work under jdk 1.8 compatibility ?
EDITED:
Maven seems to be not involved. The same problem simulated on non maven project with the same source code and compatibility level of compiler set to 1.7. Is it a bug ?
EDITED-2:
After more examination I have found that the following difference in annotation makes difference : java.lang.annotation.ElementType.TYPE_USE , when annotation does not have this, the nullcheck is not detected under Java 8, but is detected under Java 7.
But why ?! Why there is so different behaviour ?!
EDITED-3:
After research from MartinLippert and tested by me it seems that nullcheck API has drastically changed between java 7 and java 8. Null detection requires (as seen from version 2.0 of eclipse libraries) java.lang.annotation.ElementType.TYPE_USE, the types #Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER}) are ignored in analysis. SO THE QUESTION IS NOW AS FOLLOWS: WHY NULL ANALYSIS UNDER JAVA 8 REQUIRES AND WORKS ONLY UNDER NEW ELEMENT TYPE ? (I understand that with java 8 it is good to fully utilise new language features, but why was needed to break compatibility ? For example javax.validation #NotNull is now unusable as nullchecking annotation :-((( )

For Eclipse Luna, the development effort was focused on the "typical" combinations:
Java 7 and declaration annotations
Java 8 and type annotations
In this version the combination Java 8 and declaration annotations (what is requested in this question) was not fully implemented.
This has been fixed via https://bugs.eclipse.org/bugs/show_bug.cgi?id=435805
The fix is available in milestone builds towards Eclipse Mars since M4.
OTOH, I can only encourage projects using Java 8 to upgrade to type annotations for much greater expressiveness - enabling much more precise null type checking.

Related

A problem with build task and sourceCompatibility = 1.7, gradle 5.3

I use sourceCompatibility = 1.7 in gradle.properties, Gradle JVM(Settings, Gradle, Intellij IDEA) is 1.8.0_171 and
wrapper {
gradleVersion = '5.3'
distributionType = 'ALL'
}
in build.gradle.
When I run build task(Tasks->build->build Intellij IDEA) I get "error: incompatible types: inferred type does not conform to equality constraint(s)" during compileJava task.
If I set up Gradle JVM is 1.7.0_80 and
wrapper {
gradleVersion = '4.3'
distributionType = 'ALL'
}
build is fine.
What I'm doing wrong? How can I build JAVA 7 project using gradle 5.3?
This is probably caused by a subtle change in the type-checking rules for generics between Java 7 & Java 8 as described in Why does this program compile with Java 7 but not Java 8?
So what is the solution?
I doubt you will find a magic compiler switch or something to just make it work. Realistically, I think that your choices are:
You could just compile on a Java 7 platform as you did to start with. But this is not a good long-term solution.
If this is a supported product, lodge a support request.
If this is an open source product, check the product's issue checker to see if someone has already reported the problem. They may have reported a fix as well.
Look at the source code that you are trying to compile, identify the cause of the compilation error and fix it. If this is an open source product, submit your fix as a patch.
Note that it is possible that these compilation errors are reporting a latent bug that could under some circumstances lead to unexpected runtime exceptions.
You said:
I thought sourceCompatibility = 1.7 will set up java 7 for compile *.java.
It does. However, there are degrees of compatibility.
When you run a Java 8 compiler with -source 1.7 you are actually just turning off support for new language features. Under the hood, the compiler is still a Java 8 compiler. If there have been subtle changes in (for example) the type checker, it is plausible that the Java engineers did not implement a backwards compatibility mode for the old behavior.
(The extra mode may make an already complex piece of software too difficult to maintain. Type checking and type inference is one of the more difficult aspects of compiler implementation.)

Java backward compatibility doesn't work

I've created library for encoding/decoding property files. Library has two main purposes:
Encode property file and save it to another file.
Return key value from encoded file (decode file, store result as string in memory, load string to Properties object and return result from properties object).
Everything seems to work fine but today I've noticed that library doesn't work on java 1.5. I've noticed that problem occurs on decoding side so let's focus on this code. Assume that code responsible for decoding looks like that:
String props = "key1=val1\nkey2=val2";
Properties p = new Properties();
p.load(new StringReader(props));
p.list(System.out);
After few tests I saw that the problem is with this line:
p.load(new StringReader(props));
I found that Properties class in java 1.5 doesn't have load(Reader) declaration. To meet java 1.5 API requirements I changed this line to load(InputStream). Everyting works fine now but here is the question.
I use gradle to compile project and I knew that this library should work on java 1.5+ ( I've java 1.7 installed on my computer) so I added to build.gradle those two lines
sourceCompatibility = '1.5'
targetCompatibility = '1.5'
I thought that java compiler will know that I want to compile code with compatibility to java 1.5 and will show appropriate errors. To be sure that it isn't gradle problem I compiled java code from command line but with the same result (compiler doesn't show any errors). So why compiler doesn't show any errors while compiling?
Java 1.5 Properties class API: http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Properties.html
Java 1.6 Properties class API: http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html
[UPDATE]
Neither -source or -target will check API compatibility. If so how can I check it in gradle? As millimoose wrote maven has this plugin (http://mojo.codehaus.org/animal-sniffer-maven-plugin/index.html) but what with gradle?
See the sections of the javac documents named "cross-compiling" and "Cross-Compilation Example".
http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javac.html#crosscomp-options
Specifically this part:
It is important to use -bootclasspath and -extdirs when
cross-compiling; see Cross-Compilation Example below ....... If you do not
specify the correct version of bootstrap classes, the compiler will
use the old language rules (in this example, it will use version 1.6
of the Java programming language) combined with the new bootstrap
classes, which can result in class files that do not work on the older
platform (in this case, Java SE 6) because reference to non-existent
methods can get included.
The -source switch only instructs the compiler to give a compilation error if you use a language construct not supported in the specified version. For example using try-with-resources with -source 1.6 will result in a compilation error, as it is only supported in Java 7 and higher. Its use is more a sanity check (ie: is my code still compatible with Java version 1.x)
The -target switch instructs the compiler to emit byte code compatible with the specified version. That is: the compiled code can run on the virtual machines of the specified version.
However neither of these switches make the compiler check for compatibility with Java libraries of an earlier Java version. That is why since Java 7, the compiler gives a warning if you use -target 1.6 (or earlier), that you should also specify the -bootclasspath to point to a Java runtime library set of that java version so that it can check if your code is only using classes and methods of that Java version.

Programing with Eclipse for Java 6 and 7

How to setup Eclipse executions environments and projects to be developed for both JAVA 6 and JAVA 7 JREs?
I'm asking this because not all programs that are developed to run over JAVA 6 will run in JAVA 7.
In this situation for example:
I need to use a JAVA API interface, and the JAVA 6 version of this interface is:
public interface Foo {
int getValue();
}
But, the JAVA 7 version of this interface is:
public interface Foo {
int getValue();
int getNewValue();
}
Now, if I code my program like this:
public class FooImp implements Foo {
public static void main( String[] args ) {
}
public int getValue() {
return 1;
}
}
This will run work fine on JRE 6. But, if I run it over JRE 7 it will not work, because the Foo interface requires the operation getNewValue to be implemented.
In this case, what is the best solution, using Eclipse, to code my program and ensure that it will run over JRE 6 and 7? How should I configure the execution environments and run the tests?
Note: At this moment, I'm working like this:
I code my program over JDK 6, to ensure that I'm not using any resourses of JDK 7 that doest exists in JDK 6.
I run the first test over JRE 6.
Then, it export my project and run it over JRE 7.
Note 2: Searching specifically about the java.sql.ResultSet problem, I found this question:
Java 6 Source backward-compatibility and SQL
Now I see that, this problem is common with JDBC, the sourcecode compability is broken with frequency. So, I think that there is no much to discuss here. Thanks all for the answers and the effort.
I don't know if there's an elegant way to do this, but the following could work.
Reorganize and refactor your codebase into two parts:
the part that needs to work in the the Java 6 version of your codebase, and
the other code that is specific to the Java 7 version.
(This is likely to involve creating some Java interfaces with Java 6 and Java 7 specific implementation classes.)
Put the Java 6 and Java 7 code into separate Eclipse Java projects. Set the Eclipse project preferences to select the appropriate Java compliance level for each project; e.g. "Project > Properties > Java Compiler" ...
If you haven't already done so, further refactor the code so that the Java 6 and Java 7 specific classes are dynamically loaded by the core; e.g. using Class.forName(...)
This should allow you to develop the Java 6 and Java 7 parts in the same Eclipse workspace.
You should also be able to use "Run > Run Configurations" and/or "Run > Debug Configurations" to create launchers for Java 6 and Java 7 for in-Eclipse testing.
Note: I've not tried this myself. Please let us know if it works.

Compiler error with #override when overriding an interface method on JDK 7u2

I know, this sounds like an old question, which has been asked before(*). But it is a little bit different. I installed a clean system with the newest versions of JDK (7u2) and eclipse (and also AndroidSDK). When I import old projects, I get an error "The method methodName(Parameter) of type Class must override a superclass method".
The #override-tag was there (correctly), because the method overrides a method of an INTERFACE of the superclass. somehow compiler thinks that this tag is just for superclass directly. As far as i know #override is not changed in Java 7 and my project was working properly on 6. Is it a possible bug or compability problem or I am missing something?
(*)Similar problem used to happen, when compiling on JDK5, simply because #override was yet implemented in Java 5. Solution was, of course changing compiler compliance level to 6. But what is it with JDK 7 now?
You shouldn't use JDK 7 for the development. There is a requirement that you have to use JDK 5 or 6.

Why is this program causing a runtime error?

class Test {
public static void main(String args[])
{
Boolean expr = true;
if (expr)
{
System.out.println("true");
}
else
{
System.out.println("false");
}
}
}
EDIT:
the error is java.lang.UnsupportedClassVersionError
Static should be with lower case: static.
Check this link
java.lang.UnsupportedClassVersionError: why is it thrown & how to resolve?
How to resolve UnsupportedClassVersionError?
Whenever you encounter this error, do check if you’re using an earlier
version of JVM to execute the class file than the corresponding
version of compiler you used to compile the source code. The example
shown here was compiled using Java 5.0 compiler, but when I tried to
run using JVM 1.4, I got the above error. I just needed to switch
either to a JVM version 5.0 or above OR needed to switch to a Java
Compiler of JDK 1.4 or below (you of course need to make sure the
source code is compatible with the corresponding version of the
compiler otherwise you’ll start getting other compiler errors).
A higher JVM version doesn’t cause a problem in most of cases unless
the class file format is quite old (and hence doesn’t lie in the
supported range as specified by Sun for that particular JVM version
... as discussed above). But, it’s always a good practice to have both
the Compiler and the JVM of the same version.
This is no runtime error. This doesn't compile.
Use static (lowercase s) instead of Static.
static should be lower case, and I believe you need it to be a public class. Also why are you using Boolean instead of boolean, wrapper types aren't necessary here.
This code (if using JDK 5 and upwards, because of autoboxing/unboxing) runs perfectly without any RuntimeException and it will display true on the console.
I think you are compiling with one version of java and running with another. To be clearer you are compiling with a later version of java and running with an earlier version.
Are you compiling and running this class in an IDE?

Categories

Resources