Download
Getting Started
Members
Projects
Community
Marketplace
Events
Planet Eclipse
Newsletter
Videos
Participate
Report a Bug
Forums
Mailing Lists
Wiki
IRC
How to Contribute
Working Groups
Automotive
Internet of Things
LocationTech
Long-Term Support
PolarSys
Science
OpenMDM
More
Community
Marketplace
Events
Planet Eclipse
Newsletter
Videos
Participate
Report a Bug
Forums
Mailing Lists
Wiki
IRC
How to Contribute
Working Groups
Automotive
Internet of Things
LocationTech
Long-Term Support
PolarSys
Science
OpenMDM
Toggle navigation
Bugzilla – Attachment 220779 Details for
Bug 388281
[compiler][null] inheritance of null annotations as an option
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
Log In
[x]
|
Terms of Use
|
Copyright Agent
Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read
this important communication.
[patch]
Patch ported to master
Bug-388281_v1.patch (text/plain), 76.14 KB, created by
Stephan Herrmann
on 2012-09-06 07:55:07 EDT
(
hide
)
Description:
Patch ported to master
Filename:
MIME Type:
Creator:
Stephan Herrmann
Created:
2012-09-06 07:55:07 EDT
Size:
76.14 KB
patch
obsolete
>diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java >index 5df2de4..67992c1 100644 >--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java >+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java >@@ -1880,6 +1880,7 @@ > " <argument value=\"---OUTPUT_DIR_PLACEHOLDER---\"/>\n" + > " </command_line>\n" + > " <options>\n" + >+ " <option key=\"org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations\" value=\"disabled\"/>\n" + > " <option key=\"org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation\" value=\"ignore\"/>\n" + > " <option key=\"org.eclipse.jdt.core.compiler.annotation.nonnull\" value=\"org.eclipse.jdt.annotation.NonNull\"/>\n" + > " <option key=\"org.eclipse.jdt.core.compiler.annotation.nonnullbydefault\" value=\"org.eclipse.jdt.annotation.NonNullByDefault\"/>\n" + >diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java >index cf5f3b6..f76665f 100644 >--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java >+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java >@@ -403,6 +403,7 @@ > expectedProblemAttributes.put("ContradictoryNullAnnotations", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); > expectedProblemAttributes.put("ComparingIdentical", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); > expectedProblemAttributes.put("ConflictingImport", new ProblemAttributes(CategorizedProblem.CAT_IMPORT)); >+ expectedProblemAttributes.put("ConflictingNullAnnotations", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); > expectedProblemAttributes.put("ConstructorVarargsArgumentNeedCast", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); > expectedProblemAttributes.put("CorruptedSignature", new ProblemAttributes(CategorizedProblem.CAT_BUILDPATH)); > expectedProblemAttributes.put("DeadCode", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); >@@ -1107,6 +1108,7 @@ > expectedProblemAttributes.put("CodeSnippetMissingMethod", SKIP); > expectedProblemAttributes.put("ComparingIdentical", new ProblemAttributes(JavaCore.COMPILER_PB_COMPARING_IDENTICAL)); > expectedProblemAttributes.put("ConflictingImport", SKIP); >+ expectedProblemAttributes.put("ConflictingNullAnnotations", new ProblemAttributes(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION)); > expectedProblemAttributes.put("ContradictoryNullAnnotations", SKIP); > expectedProblemAttributes.put("ConstructorVarargsArgumentNeedCast", new ProblemAttributes(JavaCore.COMPILER_PB_VARARGS_ARGUMENT_NEED_CAST)); > expectedProblemAttributes.put("CorruptedSignature", SKIP); >diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java >index 38adde2..ef6f6f2 100644 >--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java >+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java >@@ -53,7 +53,7 @@ > // Static initializer to specify tests subset using TESTS_* static variables > // All specified tests which do not belong to the class are skipped... > static { >-// TESTS_NAMES = new String[] { "testBug385626" }; >+// TESTS_NAMES = new String[] { "testBug388281_08" }; > // TESTS_NUMBERS = new int[] { 561 }; > // TESTS_RANGE = new int[] { 1, 2049 }; > } >@@ -3715,4 +3715,477 @@ > null,//options > ""); > } >+ >+/* Content of Test388281.jar used in the following tests: >+ >+// === package i (explicit annotations): === >+package i; >+import org.eclipse.jdt.annotation.NonNull; >+import org.eclipse.jdt.annotation.Nullable; >+public interface I { >+ @NonNull Object m1(@Nullable Object a1); >+ @Nullable String m2(@NonNull Object a2); >+ Object m1(@Nullable Object o1, Object o2); >+} >+ >+// === package i2 with package-info.java (default annot, canceled in one type): === >+@org.eclipse.jdt.annotation.NonNullByDefault >+package i2; >+ >+package i2; >+public interface I2 { >+ Object m1(Object a1); >+ String m2(Object a2); >+} >+ >+package i2; >+public interface II extends i.I { >+ String m1(Object o1, Object o2); >+} >+ >+package i2; >+import org.eclipse.jdt.annotation.NonNullByDefault; >+@NonNullByDefault(false) >+public interface I2A { >+ Object m1(Object a1); >+ String m2(Object a2); >+} >+ >+// === package c (no null annotations): === >+package c; >+public class C1 implements i.I { >+ public Object m1(Object a1) { >+ System.out.println(a1.toString()); // (1) >+ return null; // (2) >+ } >+ public String m2(Object a2) { >+ System.out.println(a2.toString()); >+ return null; >+ } >+ public Object m1(Object o1, Object o2) { >+ return null; >+ } >+} >+ >+package c; >+public class C2 implements i2.I2 { >+ public Object m1(Object a1) { >+ return a1; >+ } >+ public String m2(Object a2) { >+ return a2.toString(); >+ } >+} >+ */ >+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=388281 >+// Test whether null annotations from a super interface are respected >+// Class and its super interface both read from binary >+public void testBug388281_01() { >+ String path = this.getCompilerTestsPluginDirectoryPath() + File.separator + "workspace" + File.separator + "Test388281.jar"; >+ String[] libs = new String[this.LIBS.length + 1]; >+ System.arraycopy(this.LIBS, 0, libs, 0, this.LIBS.length); >+ libs[this.LIBS.length] = path; >+ Map options = getCompilerOptions(); >+ options.put(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED); >+ runNegativeTest( >+ new String[] { >+ "Client.java", >+ "import c.C1;\n" + >+ "public class Client {\n" + >+ " void test(C1 c) {\n" + >+ " String s = c.m2(null); // (3)\n" + >+ " System.out.println(s.toUpperCase()); // (4)\n" + >+ " }\n" + >+ "}\n" >+ }, >+ "----------\n" + >+ "1. ERROR in Client.java (at line 4)\n" + >+ " String s = c.m2(null); // (3)\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull Object\' but the provided value is null\n" + >+ "----------\n" + >+ "2. ERROR in Client.java (at line 5)\n" + >+ " System.out.println(s.toUpperCase()); // (4)\n" + >+ " ^\n" + >+ "Potential null pointer access: The variable s may be null at this location\n" + >+ "----------\n", >+ libs, >+ true /* shouldFlush*/, >+ options); >+} >+ >+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=388281 >+// Test whether null annotations from a super interface are respected >+// Class from source, its supers (class + super interface) from binary >+public void testBug388281_02() { >+ String path = this.getCompilerTestsPluginDirectoryPath() + File.separator + "workspace" + File.separator + "Test388281.jar"; >+ String[] libs = new String[this.LIBS.length + 1]; >+ System.arraycopy(this.LIBS, 0, libs, 0, this.LIBS.length); >+ libs[this.LIBS.length] = path; >+ Map options = getCompilerOptions(); >+ options.put(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED); >+ runNegativeTest( >+ new String[] { >+ "ctest/C.java", >+ "package ctest;\n" + >+ "public class C extends c.C1 {\n" + >+ " @Override\n" + >+ " public Object m1(Object a1) {\n" + >+ " System.out.println(a1.toString()); // (1)\n" + >+ " return null; // (2)\n" + >+ " }\n" + >+ " @Override\n" + >+ " public String m2(Object a2) {\n" + >+ " System.out.println(a2.toString());\n" + >+ " return null;\n" + >+ " }\n" + >+ "}\n", >+ "Client.java", >+ "import ctest.C;\n" + >+ "public class Client {\n" + >+ " void test(C c) {\n" + >+ " String s = c.m2(null); // (3)\n" + >+ " System.out.println(s.toUpperCase()); // (4)\n" + >+ " }\n" + >+ "}\n" >+ }, >+ "----------\n" + >+ "1. ERROR in ctest\\C.java (at line 5)\n" + >+ " System.out.println(a1.toString()); // (1)\n" + >+ " ^^\n" + >+ "Potential null pointer access: The variable a1 may be null at this location\n" + >+ "----------\n" + >+ "2. ERROR in ctest\\C.java (at line 6)\n" + >+ " return null; // (2)\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull Object\' but the provided value is null\n" + >+ "----------\n" + >+ "----------\n" + >+ "1. ERROR in Client.java (at line 4)\n" + >+ " String s = c.m2(null); // (3)\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull Object\' but the provided value is null\n" + >+ "----------\n" + >+ "2. ERROR in Client.java (at line 5)\n" + >+ " System.out.println(s.toUpperCase()); // (4)\n" + >+ " ^\n" + >+ "Potential null pointer access: The variable s may be null at this location\n" + >+ "----------\n", >+ libs, >+ true /* shouldFlush*/, >+ options); >+} >+ >+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=388281 >+// Test whether null annotations from a super interface trigger an error against the overriding implementation >+// Class from source, its super interface from binary >+public void testBug388281_03() { >+ String path = this.getCompilerTestsPluginDirectoryPath() + File.separator + "workspace" + File.separator + "Test388281.jar"; >+ String[] libs = new String[this.LIBS.length + 1]; >+ System.arraycopy(this.LIBS, 0, libs, 0, this.LIBS.length); >+ libs[this.LIBS.length] = path; >+ Map options = getCompilerOptions(); >+ options.put(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED); >+ runNegativeTest( >+ new String[] { >+ "ctest/C.java", >+ "package ctest;\n" + >+ "public class C implements i.I {\n" + >+ " public Object m1(Object a1) {\n" + >+ " System.out.println(a1.toString()); // (1)\n" + >+ " return null; // (2)\n" + >+ " }\n" + >+ " public String m2(Object a2) {\n" + >+ " System.out.println(a2.toString());\n" + >+ " return null;\n" + >+ " }\n" + >+ " public Object m1(Object a1, Object a2) {\n" + >+ " System.out.println(a1.toString()); // (3)\n" + >+ " return null;\n" + >+ " }\n" + >+ "}\n" >+ }, >+ "----------\n" + >+ "1. ERROR in ctest\\C.java (at line 4)\n" + >+ " System.out.println(a1.toString()); // (1)\n" + >+ " ^^\n" + >+ "Potential null pointer access: The variable a1 may be null at this location\n" + >+ "----------\n" + >+ "2. ERROR in ctest\\C.java (at line 5)\n" + >+ " return null; // (2)\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull Object\' but the provided value is null\n" + >+ "----------\n" + >+ "3. ERROR in ctest\\C.java (at line 12)\n" + >+ " System.out.println(a1.toString()); // (3)\n" + >+ " ^^\n" + >+ "Potential null pointer access: The variable a1 may be null at this location\n" + >+ "----------\n", >+ libs, >+ true /* shouldFlush*/, >+ options); >+} >+ >+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=388281 >+// Do inherit even if one parameter/return is annotated >+// also features some basic overloading >+public void testBug388281_04() { >+ Map options = getCompilerOptions(); >+ options.put(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED); >+ runNegativeTestWithLibs( >+ true /* shouldFlush*/, >+ new String[] { >+ "i/I.java", >+ "package i;\n" + >+ "import org.eclipse.jdt.annotation.*;\n" + >+ "public interface I {\n" + >+ " @NonNull Object m1(@NonNull Object s1, @Nullable String s2);\n" + >+ " @Nullable Object m1(@Nullable String s1, @NonNull Object s2);\n" + >+ "}\n", >+ "ctest/C.java", >+ "package ctest;\n" + >+ "import org.eclipse.jdt.annotation.*;\n" + >+ "public class C implements i.I {\n" + >+ " public Object m1(@Nullable Object o1, String s2) {\n" + >+ " System.out.println(s2.toString()); // (1)\n" + >+ " return null; // (2)\n" + >+ " }\n" + >+ " public @NonNull Object m1(String s1, Object o2) {\n" + >+ " System.out.println(s1.toString()); // (3)\n" + >+ " return new Object();\n" + >+ " }\n" + >+ "}\n" >+ }, >+ options, >+ "----------\n" + >+ "1. ERROR in ctest\\C.java (at line 5)\n" + >+ " System.out.println(s2.toString()); // (1)\n" + >+ " ^^\n" + >+ "Potential null pointer access: The variable s2 may be null at this location\n" + >+ "----------\n" + >+ "2. ERROR in ctest\\C.java (at line 6)\n" + >+ " return null; // (2)\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull Object\' but the provided value is null\n" + >+ "----------\n" + >+ "3. ERROR in ctest\\C.java (at line 9)\n" + >+ " System.out.println(s1.toString()); // (3)\n" + >+ " ^^\n" + >+ "Potential null pointer access: The variable s1 may be null at this location\n" + >+ "----------\n"); >+} >+ >+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=388281 >+// Test whether null annotations from a super interface trigger an error against the overriding implementation >+// Class from source, its super interface from binary >+// Super interface subject to package level @NonNullByDefault >+public void testBug388281_05() { >+ String path = this.getCompilerTestsPluginDirectoryPath() + File.separator + "workspace" + File.separator + "Test388281.jar"; >+ String[] libs = new String[this.LIBS.length + 1]; >+ System.arraycopy(this.LIBS, 0, libs, 0, this.LIBS.length); >+ libs[this.LIBS.length] = path; >+ Map options = getCompilerOptions(); >+ options.put(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED); >+ runNegativeTest( >+ new String[] { >+ "ctest/C.java", >+ "package ctest;\n" + >+ "public class C implements i2.I2 {\n" + >+ " public Object m1(Object a1) {\n" + >+ " System.out.println(a1.toString()); // silent\n" + >+ " return null; // (1)\n" + >+ " }\n" + >+ " public String m2(Object a2) {\n" + >+ " System.out.println(a2.toString());\n" + >+ " return null; // (2)\n" + >+ " }\n" + >+ "}\n", >+ "Client.java", >+ "import ctest.C;\n" + >+ "public class Client {\n" + >+ " void test(C c) {\n" + >+ " String s = c.m2(null); // (3)\n" + >+ " }\n" + >+ "}\n" >+ }, >+ "----------\n" + >+ "1. ERROR in ctest\\C.java (at line 5)\n" + >+ " return null; // (1)\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull Object\' but the provided value is null\n" + >+ "----------\n" + >+ "2. ERROR in ctest\\C.java (at line 9)\n" + >+ " return null; // (2)\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull String\' but the provided value is null\n" + >+ "----------\n" + >+ "----------\n" + >+ "1. ERROR in Client.java (at line 4)\n" + >+ " String s = c.m2(null); // (3)\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull Object\' but the provided value is null\n" + >+ "----------\n", >+ libs, >+ true /* shouldFlush*/, >+ options); >+} >+ >+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=388281 >+// Conflicting annotations from several indirect super interfaces must be detected >+public void testBug388281_06() { >+ String path = this.getCompilerTestsPluginDirectoryPath() + File.separator + "workspace" + File.separator + "Test388281.jar"; >+ String[] libs = new String[this.LIBS.length + 1]; >+ System.arraycopy(this.LIBS, 0, libs, 0, this.LIBS.length); >+ libs[this.LIBS.length] = path; >+ Map options = getCompilerOptions(); >+ options.put(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED); >+ runNegativeTest( >+ new String[] { >+ "ctest/C.java", >+ "package ctest;\n" + >+ "public class C extends c.C2 implements i2.I2A {\n" + // neither super has explicit annotations, >+ // but C2 inherits those from the default applicable at its super interface i2.I2 >+ // whereas I2A cancels that same default >+ "}\n" >+ }, >+ "----------\n" + >+ "1. ERROR in ctest\\C.java (at line 2)\n" + >+ " public class C extends c.C2 implements i2.I2A {\n" + >+ " ^\n" + >+ "The method m2(Object) from C2 cannot implement the corresponding method from I2A due to incompatible nullness constraints\n" + >+ "----------\n" + >+ "2. ERROR in ctest\\C.java (at line 2)\n" + >+ " public class C extends c.C2 implements i2.I2A {\n" + >+ " ^\n" + >+ "The method m1(Object) from C2 cannot implement the corresponding method from I2A due to incompatible nullness constraints\n" + >+ "----------\n", >+ libs, >+ true /* shouldFlush*/, >+ options); >+} >+ >+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=388281 >+// report conflict between inheritance and default >+public void testBug388281_07() { >+ Map options = getCompilerOptions(); >+ options.put(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED); >+ runNegativeTestWithLibs( >+ new String[] { >+ "p1/Super.java", >+ "package p1;\n" + >+ "import org.eclipse.jdt.annotation.*;\n" + >+ "public class Super {\n" + >+ " public @Nullable Object m(@Nullable Object arg) {\n" + >+ " return null;" + >+ " }\n" + >+ "}\n", >+ "p2/Sub.java", >+ "package p2;\n" + >+ "import org.eclipse.jdt.annotation.*;\n" + >+ "@NonNullByDefault\n" + >+ "public class Sub extends p1.Super {\n" + >+ " @Override\n" + >+ " public Object m(Object arg) { // (a)+(b) conflict at arg and return\n" + >+ " System.out.println(arg.toString()); // (1)\n" + >+ " return null;\n" + >+ " }\n" + >+ "}\n", >+ "Client.java", >+ "public class Client {\n" + >+ " void test(p2.Sub s) {\n" + >+ " Object result = s.m(null);\n" + >+ " System.out.println(result.toString()); // (2)\n" + >+ " }\n" + >+ "}\n" >+ }, >+ options, >+ "----------\n" + >+ "1. ERROR in p2\\Sub.java (at line 6)\n" + >+ " public Object m(Object arg) { // (a)+(b) conflict at arg and return\n" + >+ " ^^^^^^\n" + >+ "The default \'@NonNull\' conflicts with the inherited \'@Nullable\' annotation in the overridden method from Super \n" + >+ "----------\n" + >+ "2. ERROR in p2\\Sub.java (at line 6)\n" + >+ " public Object m(Object arg) { // (a)+(b) conflict at arg and return\n" + >+ " ^^^\n" + >+ "The default \'@NonNull\' conflicts with the inherited \'@Nullable\' annotation in the overridden method from Super \n" + >+ "----------\n" + >+ "3. ERROR in p2\\Sub.java (at line 7)\n" + >+ " System.out.println(arg.toString()); // (1)\n" + >+ " ^^^\n" + >+ "Potential null pointer access: The variable arg may be null at this location\n" + >+ "----------\n" + >+ "----------\n" + >+ "1. ERROR in Client.java (at line 4)\n" + >+ " System.out.println(result.toString()); // (2)\n" + >+ " ^^^^^^\n" + >+ "Potential null pointer access: The variable result may be null at this location\n" + >+ "----------\n"); >+} >+ >+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=388281 >+// report conflict between inheritance and default - binary types >+public void testBug388281_08() { >+ String path = this.getCompilerTestsPluginDirectoryPath() + File.separator + "workspace" + File.separator + "Test388281.jar"; >+ String[] libs = new String[this.LIBS.length + 1]; >+ System.arraycopy(this.LIBS, 0, libs, 0, this.LIBS.length); >+ libs[this.LIBS.length] = path; >+ Map options = getCompilerOptions(); >+ options.put(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED); >+ runNegativeTest( >+ new String[] { >+ "ctest/Ctest.java", >+ "package ctest;\n" + >+ "import org.eclipse.jdt.annotation.*;\n" + >+ "@NonNullByDefault\n" + >+ "public class Ctest implements i2.II {\n" + >+ " public Object m1(@Nullable Object a1) { // silent: conflict at a1 avoided\n" + >+ " return new Object();\n" + >+ " }\n" + >+ " public String m2(Object a2) { // (a) conflict at return\n" + >+ " return null;\n" + >+ " }\n" + >+ " public String m1(Object o1, Object o2) { // (b) conflict at o1\n" + >+ " System.out.println(o1.toString()); // (1) inherited @Nullable\n" + >+ " return null; // (2) @NonNullByDefault in i2.II\n" + >+ " }\n" + >+ "}\n", >+ "Client.java", >+ "public class Client {\n" + >+ " void test(ctest.Ctest c) {\n" + >+ " Object result = c.m1(null, null); // (3) 2nd arg @NonNullByDefault from i2.II\n" + >+ " }\n" + >+ "}\n" >+ }, >+ "----------\n" + >+ "1. ERROR in ctest\\Ctest.java (at line 8)\n" + >+ " public String m2(Object a2) { // (a) conflict at return\n" + >+ " ^^^^^^\n" + >+ "The default \'@NonNull\' conflicts with the inherited \'@Nullable\' annotation in the overridden method from I \n" + >+ "----------\n" + >+ "2. ERROR in ctest\\Ctest.java (at line 11)\n" + >+ " public String m1(Object o1, Object o2) { // (b) conflict at o1\n" + >+ " ^^\n" + >+ "The default \'@NonNull\' conflicts with the inherited \'@Nullable\' annotation in the overridden method from II \n" + >+ "----------\n" + >+ "3. ERROR in ctest\\Ctest.java (at line 12)\n" + >+ " System.out.println(o1.toString()); // (1) inherited @Nullable\n" + >+ " ^^\n" + >+ "Potential null pointer access: The variable o1 may be null at this location\n" + >+ "----------\n" + >+ "4. ERROR in ctest\\Ctest.java (at line 13)\n" + >+ " return null; // (2) @NonNullByDefault in i2.II\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull String\' but the provided value is null\n" + >+ "----------\n" + >+ "----------\n" + >+ "1. ERROR in Client.java (at line 3)\n" + >+ " Object result = c.m1(null, null); // (3) 2nd arg @NonNullByDefault from i2.II\n" + >+ " ^^^^\n" + >+ "Null type mismatch: required \'@NonNull Object\' but the provided value is null\n" + >+ "----------\n", >+ libs, >+ true, // should flush >+ options); >+} > } >diff --git a/org.eclipse.jdt.core.tests.compiler/workspace/Test388281.jar b/org.eclipse.jdt.core.tests.compiler/workspace/Test388281.jar >new file mode 100644 >index 0000000..7c66b72 >--- /dev/null >+++ b/org.eclipse.jdt.core.tests.compiler/workspace/Test388281.jar >Binary files differ >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java >index 46093f9..d0a75f6 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java >@@ -1503,6 +1503,8 @@ > int SpecdNonNullLocalVariableComparisonYieldsFalse = Internal + 932; > /** @since 3.8 */ > int RequiredNonNullButProvidedSpecdNullable = Internal + 933; >+ /** @since 3.9 */ >+ int ConflictingNullAnnotations = MethodRelated + 939; > > /** > * External problems -- These are problems defined by other plugins >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java >index d4f065e..c892fac 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java >@@ -84,8 +84,10 @@ > argument.createBinding(this.scope, this.binding.parameters[i]); > // createBinding() has resolved annotations, now transfer nullness info from the argument to the method: > if ((argument.binding.tagBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) != 0) { >- if (this.binding.parameterNonNullness == null) >+ if (this.binding.parameterNonNullness == null) { > this.binding.parameterNonNullness = new Boolean[this.arguments.length]; >+ this.binding.tagBits |= TagBits.IsNullnessKnown; >+ } > this.binding.parameterNonNullness[i] = Boolean.valueOf((argument.binding.tagBits & TagBits.AnnotationNonNull) != 0); > } > } >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java >index dddb1a0..ad54d14 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java >@@ -35,6 +35,7 @@ > import org.eclipse.jdt.internal.compiler.lookup.InvocationSite; > import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; > import org.eclipse.jdt.internal.compiler.lookup.MissingTypeBinding; >+import org.eclipse.jdt.internal.compiler.lookup.ImplicitNullAnnotationVerifier; > import org.eclipse.jdt.internal.compiler.lookup.PolymorphicMethodBinding; > import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding; > import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons; >@@ -539,6 +540,12 @@ > return null; > } > >+ if (compilerOptions.isAnnotationBasedNullAnalysisEnabled && (this.binding.tagBits & TagBits.IsNullnessKnown) == 0) { >+ // not interested in reporting problems against this.binding: >+ new ImplicitNullAnnotationVerifier(compilerOptions.inheritNullAnnotations) >+ .checkImplicitNullAnnotations(this.binding, null/*srcMethod*/, false, scope); >+ } >+ > if (((this.bits & ASTNode.InsideExpressionStatement) != 0) > && this.binding.isPolymorphic()) { > // we only set the return type to be void if this method invocation is used inside an expression statement >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java >index ba4a32e..8a574f4 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java >@@ -161,6 +161,7 @@ > static final char[][] DEFAULT_NONNULL_ANNOTATION_NAME = CharOperation.splitOn('.', "org.eclipse.jdt.annotation.NonNull".toCharArray()); //$NON-NLS-1$ > static final char[][] DEFAULT_NONNULLBYDEFAULT_ANNOTATION_NAME = CharOperation.splitOn('.', "org.eclipse.jdt.annotation.NonNullByDefault".toCharArray()); //$NON-NLS-1$ > public static final String OPTION_ReportMissingNonNullByDefaultAnnotation = "org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation"; //$NON-NLS-1$ >+ public static final String OPTION_InheritNullAnnotations = "org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations"; //$NON-NLS-1$ > /** > * Possible values for configurable options > */ >@@ -423,6 +424,8 @@ > String tolerateIllegalAmbiguousVarargs = System.getProperty("tolerateIllegalAmbiguousVarargsInvocation"); //$NON-NLS-1$ > tolerateIllegalAmbiguousVarargsInvocation = tolerateIllegalAmbiguousVarargs != null && tolerateIllegalAmbiguousVarargs.equalsIgnoreCase("true"); //$NON-NLS-1$ > } >+ /** Should null annotations of overridden methods be inherited? */ >+ public boolean inheritNullAnnotations; > > // keep in sync with warningTokenToIrritant and warningTokenFromIrritant > public final static String[] warningTokens = { >@@ -809,7 +812,8 @@ > OPTION_ReportNullSpecViolation, > OPTION_ReportNullAnnotationInferenceConflict, > OPTION_ReportNullUncheckedConversion, >- OPTION_ReportRedundantNullAnnotation >+ OPTION_ReportRedundantNullAnnotation, >+ OPTION_InheritNullAnnotations > }; > return result; > } >@@ -1105,6 +1109,7 @@ > optionsMap.put(OPTION_NonNullAnnotationName, String.valueOf(CharOperation.concatWith(this.nonNullAnnotationName, '.'))); > optionsMap.put(OPTION_NonNullByDefaultAnnotationName, String.valueOf(CharOperation.concatWith(this.nonNullByDefaultAnnotationName, '.'))); > optionsMap.put(OPTION_ReportMissingNonNullByDefaultAnnotation, getSeverityString(MissingNonNullByDefaultAnnotation)); >+ optionsMap.put(OPTION_InheritNullAnnotations, this.inheritNullAnnotations ? ENABLED : DISABLED); > return optionsMap; > } > >@@ -1263,6 +1268,7 @@ > this.nonNullAnnotationName = DEFAULT_NONNULL_ANNOTATION_NAME; > this.nonNullByDefaultAnnotationName = DEFAULT_NONNULLBYDEFAULT_ANNOTATION_NAME; > this.intendedDefaultNonNullness = 0; >+ this.inheritNullAnnotations = false; > > this.analyseResourceLeaks = true; > >@@ -1592,6 +1598,9 @@ > this.nonNullByDefaultAnnotationName = CharOperation.splitAndTrimOn('.', ((String)optionValue).toCharArray()); > } > if ((optionValue = optionsMap.get(OPTION_ReportMissingNonNullByDefaultAnnotation)) != null) updateSeverity(MissingNonNullByDefaultAnnotation, optionValue); >+ if ((optionValue = optionsMap.get(OPTION_InheritNullAnnotations)) != null) { >+ this.inheritNullAnnotations = ENABLED.equals(optionValue); >+ } > } > > // Javadoc options >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java >index 4d6f01c..4058a6f 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java >@@ -1162,13 +1162,6 @@ > if (nullableAnnotationName == null || nonNullAnnotationName == null || nonNullByDefaultAnnotationName == null) > return; // not well-configured to use null annotations > >- int currentDefault = NO_NULL_DEFAULT; >- if ((this.tagBits & TagBits.AnnotationNonNullByDefault) != 0) { >- currentDefault = NONNULL_BY_DEFAULT; >- } else if ((this.tagBits & TagBits.AnnotationNullUnspecifiedByDefault) != 0) { >- currentDefault = NULL_UNSPECIFIED_BY_DEFAULT; >- } >- > // return: > IBinaryAnnotation[] annotations = method.getAnnotations(); > boolean explicitNullness = false; >@@ -1180,7 +1173,6 @@ > char[][] typeName = CharOperation.splitOn('/', annotationTypeName, 1, annotationTypeName.length-1); // cut of leading 'L' and trailing ';' > if (CharOperation.equals(typeName, nonNullByDefaultAnnotationName)) { > methodBinding.tagBits |= TagBits.AnnotationNonNullByDefault; >- currentDefault = NONNULL_BY_DEFAULT; > } > if (!explicitNullness && CharOperation.equals(typeName, nonNullAnnotationName)) { > methodBinding.tagBits |= TagBits.AnnotationNonNull; >@@ -1192,19 +1184,13 @@ > } > } > } >- if (!explicitNullness >- && (methodBinding.returnType != null && !methodBinding.returnType.isBaseType()) >- && currentDefault == NONNULL_BY_DEFAULT) { >- methodBinding.tagBits |= TagBits.AnnotationNonNull; >- } > > // parameters: > TypeBinding[] parameters = methodBinding.parameters; > int numVisibleParams = parameters.length; > int numParamAnnotations = method.getAnnotatedParametersCount(); >- if (numParamAnnotations > 0 || currentDefault == NONNULL_BY_DEFAULT) { >+ if (numParamAnnotations > 0) { > for (int j = 0; j < numVisibleParams; j++) { >- explicitNullness = false; > if (numParamAnnotations > 0) { > int startIndex = numParamAnnotations - numVisibleParams; > IBinaryAnnotation[] paramAnnotations = method.getParameterAnnotations(j+startIndex); >@@ -1218,23 +1204,14 @@ > if (methodBinding.parameterNonNullness == null) > methodBinding.parameterNonNullness = new Boolean[numVisibleParams]; > methodBinding.parameterNonNullness[j] = Boolean.TRUE; >- explicitNullness = true; > break; > } else if (CharOperation.equals(typeName, nullableAnnotationName)) { > if (methodBinding.parameterNonNullness == null) > methodBinding.parameterNonNullness = new Boolean[numVisibleParams]; > methodBinding.parameterNonNullness[j] = Boolean.FALSE; >- explicitNullness = true; > break; > } > } >- } >- } >- if (!explicitNullness && currentDefault == NONNULL_BY_DEFAULT) { >- if (methodBinding.parameterNonNullness == null) >- methodBinding.parameterNonNullness = new Boolean[numVisibleParams]; >- if (methodBinding.parameters[j]!= null && !methodBinding.parameters[j].isBaseType()) { >- methodBinding.parameterNonNullness[j] = Boolean.TRUE; > } > } > } >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java >new file mode 100644 >index 0000000..77350a4 >--- /dev/null >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java >@@ -0,0 +1,363 @@ >+/******************************************************************************* >+ * Copyright (c) 2012 GK Software AG and others. >+ * All rights reserved. This program and the accompanying materials >+ * are made available under the terms of the Eclipse Public License v1.0 >+ * which accompanies this distribution, and is available at >+ * http://www.eclipse.org/legal/epl-v10.html >+ * >+ * Contributors: >+ * Stephan Herrmann - initial API and implementation >+ *******************************************************************************/ >+package org.eclipse.jdt.internal.compiler.lookup; >+ >+import java.util.ArrayList; >+import java.util.HashSet; >+import java.util.List; >+import java.util.Set; >+ >+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; >+import org.eclipse.jdt.internal.compiler.ast.Argument; >+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; >+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; >+ >+/** >+ * Extracted slice from MethodVerifier15, which is responsible only for implicit null annotations. >+ * First, if enabled, it detects overridden methods from which null annotations are inherited. >+ * Next, also default nullness is filled into remaining empty slots. >+ * After all implicit annotations have been filled in compatibility is checked and problems are complained. >+ */ >+public class ImplicitNullAnnotationVerifier { >+ >+ // delegate which to ask for recursive analysis of super methods >+ // can be 'this', but is never a MethodVerifier (to avoid infinite recursion). >+ ImplicitNullAnnotationVerifier buddyImplicitNullAnnotationsVerifier; >+ private boolean inheritNullAnnotations; >+ >+ public ImplicitNullAnnotationVerifier(boolean inheritNullAnnotations) { >+ this.buddyImplicitNullAnnotationsVerifier = this; >+ this.inheritNullAnnotations = inheritNullAnnotations; >+ } >+ >+ // for sub-classes: >+ ImplicitNullAnnotationVerifier(CompilerOptions options) { >+ this.buddyImplicitNullAnnotationsVerifier = new ImplicitNullAnnotationVerifier(options.inheritNullAnnotations); >+ this.inheritNullAnnotations = options.inheritNullAnnotations; >+ } >+ >+ /** >+ * Check and fill in implicit annotations from overridden methods and from default. >+ * Precondition: caller has checked whether annotation-based null analysis is enabled. >+ */ >+ public void checkImplicitNullAnnotations(MethodBinding currentMethod, AbstractMethodDeclaration srcMethod, boolean complain, Scope scope) { >+ // check inherited nullness from superclass and superInterfaces >+ try { >+ ReferenceBinding currentType = currentMethod.declaringClass; >+ if (currentType.id == TypeIds.T_JavaLangObject) { >+ return; >+ } >+ boolean needToApplyNonNullDefault = currentMethod.hasNonNullDefault(); >+ // compatibility & inheritance do not consider constructors / static methods: >+ boolean isInstanceMethod = !currentMethod.isConstructor() && !currentMethod.isStatic(); >+ complain &= isInstanceMethod; >+ if (!needToApplyNonNullDefault >+ && !complain >+ && !(this.inheritNullAnnotations && isInstanceMethod)) { >+ return; // short cut, no work to be done >+ } >+ >+ if (isInstanceMethod) { >+ List superMethodList = new ArrayList(); >+ >+ findAllOverriddenMethods(currentMethod.original(), currentMethod.selector, currentMethod.parameters.length, >+ currentType, new HashSet(), superMethodList); >+ >+ int length = superMethodList.size(); >+ for (int i = length; --i >= 0;) { >+ MethodBinding currentSuper = (MethodBinding) superMethodList.get(i); >+ if ((currentSuper.tagBits & TagBits.IsNullnessKnown) == 0) { >+ // recurse to prepare currentSuper >+ checkImplicitNullAnnotations(currentSuper, null, false, scope); // TODO (stephan) complain=true if currentSuper is source method?? >+ } >+ checkNullSpecInheritance(currentMethod, srcMethod, needToApplyNonNullDefault, complain, currentSuper, scope); >+ needToApplyNonNullDefault = false; >+ } >+ } >+ if (needToApplyNonNullDefault) { >+ currentMethod.fillInDefaultNonNullness(srcMethod); >+ } >+ } finally { >+ currentMethod.tagBits |= TagBits.IsNullnessKnown; >+ } >+ } >+ >+ /* >+ * Recursively traverse the tree of ancestors but whenever we find a matching method prune the super tree. >+ * Collect all matching methods in 'result'. >+ */ >+ private void findAllOverriddenMethods(MethodBinding original, char[] selector, int suggestedParameterLength, >+ ReferenceBinding currentType, Set ifcsSeen, List result) >+ { >+ if (currentType.id == TypeIds.T_JavaLangObject) >+ return; >+ >+ // superclass: >+ collectOverriddenMethods(original, selector, suggestedParameterLength, currentType.superclass(), ifcsSeen, result); >+ >+ // superInterfaces: >+ ReferenceBinding[] superInterfaces = currentType.superInterfaces(); >+ int ifcLen = superInterfaces.length; >+ for (int i = 0; i < ifcLen; i++) { >+ ReferenceBinding currentIfc = superInterfaces[i]; >+ if (ifcsSeen.add(currentIfc.original())) { // process each interface at most once >+ collectOverriddenMethods(original, selector, suggestedParameterLength, currentIfc, ifcsSeen, result); >+ } >+ } >+ } >+ >+ /* collect matching methods from one supertype. */ >+ private void collectOverriddenMethods(MethodBinding original, char[] selector, int suggestedParameterLength, >+ ReferenceBinding superType, Set ifcsSeen, List result) >+ { >+ MethodBinding [] ifcMethods = superType.getMethods(selector, suggestedParameterLength); >+ int length = ifcMethods.length; >+ for (int i=0; i<length; i++) { >+ MethodBinding currentMethod = ifcMethods[i]; >+ if (currentMethod.isStatic()) >+ continue; >+ if (areParametersEqual(original, currentMethod.original())) { >+ result.add(currentMethod); >+ return; // at most one method is overridden from any supertype >+ } >+ } >+ findAllOverriddenMethods(original, selector, suggestedParameterLength, superType, ifcsSeen, result); >+ } >+ >+ /* The main algorithm in this class */ >+ void checkNullSpecInheritance(MethodBinding currentMethod, AbstractMethodDeclaration srcMethod, >+ boolean hasNonNullDefault, boolean shouldComplain, >+ MethodBinding inheritedMethod, Scope scope) >+ { >+ // Note that basically two different flows lead into this method: >+ // (1) during MethodVerifyer15.checkMethods() we want to report errors (against srcMethod or against the current type) >+ // In this case this method is directly called from MethodVerifier15 (checkAgainstInheritedMethod / checkConcreteInheritedMethod) >+ // (2) during on-demand invocation we are mainly interested in the side effects of copying inherited null annotations >+ // In this case this method is called via checkImplicitNullAnnotations from >+ // - MessageSend.resolveType(..) >+ // - SourceTypeBinding.createArgumentBindings(..) >+ // - recursive calls within this class >+ // Still we *might* want to complain about problems found (controlled by 'complain') >+ >+ if ((inheritedMethod.tagBits & TagBits.IsNullnessKnown) == 0) { >+ // TODO (stephan): even here we may need to report problems? How to discriminate? >+ this.buddyImplicitNullAnnotationsVerifier.checkImplicitNullAnnotations(inheritedMethod, null, false, scope); >+ } >+ long inheritedBits = inheritedMethod.tagBits; >+ long inheritedNullnessBits = inheritedBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable); >+ long currentBits = currentMethod.tagBits; >+ long currentNullnessBits = currentBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable); >+ >+ LookupEnvironment environment = scope.environment(); >+ boolean shouldInherit = this.inheritNullAnnotations; >+ >+ // return type: >+ returnType: { >+ if (currentMethod.returnType == null || currentMethod.returnType.isBaseType()) >+ break returnType; // no nullness for primitive types >+ if (currentNullnessBits == 0) { >+ // unspecified, may fill in either from super or from default >+ if (shouldInherit) { >+ if (inheritedNullnessBits != 0) { >+ if (hasNonNullDefault) { >+ // both inheritance and default: check for conflict? >+ if (shouldComplain && inheritedNullnessBits == TagBits.AnnotationNullable) >+ scope.problemReporter().conflictingNullAnnotations(currentMethod, ((MethodDeclaration) srcMethod).returnType, inheritedMethod); >+ // still use the inherited bits to avoid incompatibility >+ } >+ currentMethod.tagBits |= inheritedNullnessBits; >+ break returnType; // compatible by construction, skip complain phase below >+ } >+ } >+ if (hasNonNullDefault) { // conflict with inheritance already checked >+ currentMethod.tagBits |= (currentNullnessBits = TagBits.AnnotationNonNull); >+ } >+ } >+ if (shouldComplain) { >+ if ((inheritedNullnessBits & TagBits.AnnotationNonNull) != 0 >+ && currentNullnessBits != TagBits.AnnotationNonNull) >+ { >+ if (srcMethod != null) { >+ scope.problemReporter().illegalReturnRedefinition(srcMethod, inheritedMethod, >+ environment.getNonNullAnnotationName()); >+ } else { >+ scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); >+ return; >+ } >+ } >+ } >+ } >+ >+ // parameters: >+ Argument[] currentArguments = srcMethod == null ? null : srcMethod.arguments; >+ >+ int length = 0; >+ if (currentArguments != null) >+ length = currentArguments.length; >+ else if (inheritedMethod.parameterNonNullness != null) >+ length = inheritedMethod.parameterNonNullness.length; >+ else if (currentMethod.parameterNonNullness != null) >+ length = currentMethod.parameterNonNullness.length; >+ >+ for (int i = 0; i < length; i++) { >+ if (currentMethod.parameters[i].isBaseType()) continue; >+ >+ Argument currentArgument = currentArguments == null >+ ? null : currentArguments[i]; >+ Boolean inheritedNonNullNess = (inheritedMethod.parameterNonNullness == null) >+ ? null : inheritedMethod.parameterNonNullness[i]; >+ Boolean currentNonNullNess = (currentMethod.parameterNonNullness == null) >+ ? null : currentMethod.parameterNonNullness[i]; >+ >+ if (currentNonNullNess == null) { >+ // unspecified, may fill in either from super or from default >+ if (inheritedNonNullNess != null) { >+ if (shouldInherit) { >+ if (hasNonNullDefault) { >+ // both inheritance and default: check for conflict? >+ if (shouldComplain >+ && inheritedNonNullNess == Boolean.FALSE >+ && currentArgument != null) >+ { >+ scope.problemReporter().conflictingNullAnnotations(currentMethod, currentArgument, inheritedMethod); >+ } >+ // still use the inherited info to avoid incompatibility >+ } >+ if (currentMethod.parameterNonNullness == null) >+ currentMethod.parameterNonNullness = new Boolean[length]; >+ currentMethod.parameterNonNullness[i] = inheritedNonNullNess; >+ continue; // compatible by construction, skip complain phase below >+ } >+ } >+ if (hasNonNullDefault) { // conflict with inheritance already checked >+ if (currentMethod.parameterNonNullness == null) >+ currentMethod.parameterNonNullness = new Boolean[length]; >+ currentMethod.parameterNonNullness[i] = (currentNonNullNess = Boolean.TRUE); >+ } >+ } >+ if (shouldComplain) { >+ boolean needNonNull = false; >+ char[][] annotationName; >+ if (inheritedNonNullNess == Boolean.TRUE) { >+ needNonNull = true; >+ annotationName = environment.getNonNullAnnotationName(); >+ } else { >+ annotationName = environment.getNullableAnnotationName(); >+ } >+ if (inheritedNonNullNess != Boolean.TRUE // super parameter is not restricted to @NonNull >+ && currentNonNullNess == Boolean.TRUE) // current parameter is restricted to @NonNull >+ { >+ // incompatible >+ if (currentArgument != null) { >+ scope.problemReporter().illegalRedefinitionToNonNullParameter( >+ currentArgument, >+ inheritedMethod.declaringClass, >+ (inheritedNonNullNess == null) ? null : environment.getNullableAnnotationName()); >+ } else { >+ scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); >+ } >+ } else if (inheritedNonNullNess != null >+ && currentNonNullNess == null) >+ { >+ // weak conflict (TODO reconsider this case) >+ if (currentArgument != null) { >+ scope.problemReporter().parameterLackingNullAnnotation( >+ currentArgument, >+ inheritedMethod.declaringClass, >+ needNonNull, >+ annotationName); >+ } else { >+ scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); >+ } >+ } >+ } >+ } >+ } >+ >+ // ==== minimal set of utility methods previously from MethodVerifier15: ==== >+ >+ boolean areParametersEqual(MethodBinding one, MethodBinding two) { >+ TypeBinding[] oneArgs = one.parameters; >+ TypeBinding[] twoArgs = two.parameters; >+ if (oneArgs == twoArgs) return true; >+ >+ int length = oneArgs.length; >+ if (length != twoArgs.length) return false; >+ >+ >+ // methods with raw parameters are considered equal to inherited methods >+ // with parameterized parameters for backwards compatibility, need a more complex check >+ int i; >+ foundRAW: for (i = 0; i < length; i++) { >+ if (!areTypesEqual(oneArgs[i], twoArgs[i])) { >+ if (oneArgs[i].leafComponentType().isRawType()) { >+ if (oneArgs[i].dimensions() == twoArgs[i].dimensions() && oneArgs[i].leafComponentType().isEquivalentTo(twoArgs[i].leafComponentType())) { >+ // raw mode does not apply if the method defines its own type variables >+ if (one.typeVariables != Binding.NO_TYPE_VARIABLES) >+ return false; >+ // one parameter type is raw, hence all parameters types must be raw or non generic >+ // otherwise we have a mismatch check backwards >+ for (int j = 0; j < i; j++) >+ if (oneArgs[j].leafComponentType().isParameterizedTypeWithActualArguments()) >+ return false; >+ // switch to all raw mode >+ break foundRAW; >+ } >+ } >+ return false; >+ } >+ } >+ // all raw mode for remaining parameters (if any) >+ for (i++; i < length; i++) { >+ if (!areTypesEqual(oneArgs[i], twoArgs[i])) { >+ if (oneArgs[i].leafComponentType().isRawType()) >+ if (oneArgs[i].dimensions() == twoArgs[i].dimensions() && oneArgs[i].leafComponentType().isEquivalentTo(twoArgs[i].leafComponentType())) >+ continue; >+ return false; >+ } else if (oneArgs[i].leafComponentType().isParameterizedTypeWithActualArguments()) { >+ return false; // no remaining parameter can be a Parameterized type (if one has been converted then all RAW types must be converted) >+ } >+ } >+ return true; >+ } >+ boolean areTypesEqual(TypeBinding one, TypeBinding two) { >+ if (one == two) return true; >+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=329584 >+ switch(one.kind()) { >+ case Binding.TYPE: >+ switch (two.kind()) { >+ case Binding.PARAMETERIZED_TYPE: >+ case Binding.RAW_TYPE: >+ if (one == two.erasure()) >+ return true; >+ } >+ break; >+ case Binding.RAW_TYPE: >+ case Binding.PARAMETERIZED_TYPE: >+ switch(two.kind()) { >+ case Binding.TYPE: >+ if (one.erasure() == two) >+ return true; >+ } >+ } >+ >+ // need to consider X<?> and X<? extends Object> as the same 'type' >+ if (one.isParameterizedType() && two.isParameterizedType()) >+ return one.isEquivalentTo(two) && two.isEquivalentTo(one); >+ >+ // Can skip this since we resolved each method before comparing it, see computeSubstituteMethod() >+ // if (one instanceof UnresolvedReferenceBinding) >+ // return ((UnresolvedReferenceBinding) one).resolvedType == two; >+ // if (two instanceof UnresolvedReferenceBinding) >+ // return ((UnresolvedReferenceBinding) two).resolvedType == one; >+ return false; // all other type bindings are identical >+ } >+} >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java >index ce25e57..9336120 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java >@@ -451,10 +451,9 @@ > /** > * After method verifier has finished, fill in missing @NonNull specification from the applicable default. > */ >-protected void fillInDefaultNonNullness() { >+protected void fillInDefaultNonNullness(AbstractMethodDeclaration sourceMethod) { > if (this.parameterNonNullness == null) > this.parameterNonNullness = new Boolean[this.parameters.length]; >- AbstractMethodDeclaration sourceMethod = sourceMethod(); > boolean added = false; > int length = this.parameterNonNullness.length; > for (int i = 0; i < length; i++) { >@@ -466,7 +465,7 @@ > if (sourceMethod != null) { > sourceMethod.arguments[i].binding.tagBits |= TagBits.AnnotationNonNull; > } >- } else if (this.parameterNonNullness[i].booleanValue()) { >+ } else if (sourceMethod != null && this.parameterNonNullness[i].booleanValue()) { > sourceMethod.scope.problemReporter().nullAnnotationIsRedundant(sourceMethod, i); > } > } >@@ -477,7 +476,7 @@ > && (this.tagBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) == 0) > { > this.tagBits |= TagBits.AnnotationNonNull; >- } else if ((this.tagBits & TagBits.AnnotationNonNull) != 0) { >+ } else if (sourceMethod != null && (this.tagBits & TagBits.AnnotationNonNull) != 0) { > sourceMethod.scope.problemReporter().nullAnnotationIsRedundant(sourceMethod, -1/*signifies method return*/); > } > } >@@ -1170,4 +1169,11 @@ > public TypeVariableBinding[] typeVariables() { > return this.typeVariables; > } >+public boolean hasNonNullDefault() { >+ if ((this.tagBits & TagBits.AnnotationNonNullByDefault) != 0) >+ return true; >+ if ((this.tagBits & TagBits.AnnotationNullUnspecifiedByDefault) != 0) >+ return false; >+ return this.declaringClass.hasNonNullDefault(); >+} > } >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java >index aced6a1..cdae6a4 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java >@@ -18,7 +18,7 @@ > import org.eclipse.jdt.internal.compiler.util.HashtableOfObject; > import org.eclipse.jdt.internal.compiler.util.SimpleSet; > >-public class MethodVerifier { >+public class MethodVerifier extends ImplicitNullAnnotationVerifier { > SourceTypeBinding type; > HashtableOfObject inheritedMethods; > HashtableOfObject currentMethods; >@@ -42,6 +42,7 @@ > - defining an interface as a local type (local types can only be classes) > */ > MethodVerifier(LookupEnvironment environment) { >+ super(environment.globalOptions); > this.type = null; // Initialized with the public method verify(SourceTypeBinding) > this.inheritedMethods = null; > this.currentMethods = null; >@@ -52,18 +53,6 @@ > } > boolean areMethodsCompatible(MethodBinding one, MethodBinding two) { > return isParameterSubsignature(one, two) && areReturnTypesCompatible(one, two); >-} >-boolean areParametersEqual(MethodBinding one, MethodBinding two) { >- TypeBinding[] oneArgs = one.parameters; >- TypeBinding[] twoArgs = two.parameters; >- if (oneArgs == twoArgs) return true; >- >- int length = oneArgs.length; >- if (length != twoArgs.length) return false; >- >- for (int i = 0; i < length; i++) >- if (!areTypesEqual(oneArgs[i], twoArgs[i])) return false; >- return true; > } > boolean areReturnTypesCompatible(MethodBinding one, MethodBinding two) { > if (one.returnType == two.returnType) return true; >@@ -86,19 +75,6 @@ > return two.returnType.isCompatibleWith(one.returnType); // interface methods inherit from Object > > return one.returnType.isCompatibleWith(two.returnType); >-} >-boolean areTypesEqual(TypeBinding one, TypeBinding two) { >- if (one == two) return true; >- >- // its possible that an UnresolvedReferenceBinding can be compared to its resolved type >- // when they're both UnresolvedReferenceBindings then they must be identical like all other types >- // all wrappers of UnresolvedReferenceBindings are converted as soon as the type is resolved >- // so its not possible to have 2 arrays where one is UnresolvedX[] and the other is X[] >- if (one instanceof UnresolvedReferenceBinding) >- return ((UnresolvedReferenceBinding) one).resolvedType == two; >- if (two instanceof UnresolvedReferenceBinding) >- return ((UnresolvedReferenceBinding) two).resolvedType == one; >- return false; // all other type bindings are identical > } > boolean canSkipInheritedMethods() { > if (this.type.superclass() != null && this.type.superclass().isAbstract()) >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier15.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier15.java >index 9c030ec..f312a07 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier15.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier15.java >@@ -41,50 +41,6 @@ > > return isParameterSubsignature(one, two); > } >-boolean areParametersEqual(MethodBinding one, MethodBinding two) { >- TypeBinding[] oneArgs = one.parameters; >- TypeBinding[] twoArgs = two.parameters; >- if (oneArgs == twoArgs) return true; >- >- int length = oneArgs.length; >- if (length != twoArgs.length) return false; >- >- >- // methods with raw parameters are considered equal to inherited methods >- // with parameterized parameters for backwards compatibility, need a more complex check >- int i; >- foundRAW: for (i = 0; i < length; i++) { >- if (!areTypesEqual(oneArgs[i], twoArgs[i])) { >- if (oneArgs[i].leafComponentType().isRawType()) { >- if (oneArgs[i].dimensions() == twoArgs[i].dimensions() && oneArgs[i].leafComponentType().isEquivalentTo(twoArgs[i].leafComponentType())) { >- // raw mode does not apply if the method defines its own type variables >- if (one.typeVariables != Binding.NO_TYPE_VARIABLES) >- return false; >- // one parameter type is raw, hence all parameters types must be raw or non generic >- // otherwise we have a mismatch check backwards >- for (int j = 0; j < i; j++) >- if (oneArgs[j].leafComponentType().isParameterizedTypeWithActualArguments()) >- return false; >- // switch to all raw mode >- break foundRAW; >- } >- } >- return false; >- } >- } >- // all raw mode for remaining parameters (if any) >- for (i++; i < length; i++) { >- if (!areTypesEqual(oneArgs[i], twoArgs[i])) { >- if (oneArgs[i].leafComponentType().isRawType()) >- if (oneArgs[i].dimensions() == twoArgs[i].dimensions() && oneArgs[i].leafComponentType().isEquivalentTo(twoArgs[i].leafComponentType())) >- continue; >- return false; >- } else if (oneArgs[i].leafComponentType().isParameterizedTypeWithActualArguments()) { >- return false; // no remaining parameter can be a Parameterized type (if one has been converted then all RAW types must be converted) >- } >- } >- return true; >-} > boolean areReturnTypesCompatible(MethodBinding one, MethodBinding two) { > if (one.returnType == two.returnType) return true; > if (this.type.scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5) { >@@ -92,38 +48,6 @@ > } else { > return areTypesEqual(one.returnType.erasure(), two.returnType.erasure()); > } >-} >-boolean areTypesEqual(TypeBinding one, TypeBinding two) { >- if (one == two) return true; >- // https://bugs.eclipse.org/bugs/show_bug.cgi?id=329584 >- switch(one.kind()) { >- case Binding.TYPE: >- switch (two.kind()) { >- case Binding.PARAMETERIZED_TYPE: >- case Binding.RAW_TYPE: >- if (one == two.erasure()) >- return true; >- } >- break; >- case Binding.RAW_TYPE: >- case Binding.PARAMETERIZED_TYPE: >- switch(two.kind()) { >- case Binding.TYPE: >- if (one.erasure() == two) >- return true; >- } >- } >- >- // need to consider X<?> and X<? extends Object> as the same 'type' >- if (one.isParameterizedType() && two.isParameterizedType()) >- return one.isEquivalentTo(two) && two.isEquivalentTo(one); >- >- // Can skip this since we resolved each method before comparing it, see computeSubstituteMethod() >- // if (one instanceof UnresolvedReferenceBinding) >- // return ((UnresolvedReferenceBinding) one).resolvedType == two; >- // if (two instanceof UnresolvedReferenceBinding) >- // return ((UnresolvedReferenceBinding) two).resolvedType == one; >- return false; // all other type bindings are identical > } > // Given `overridingMethod' which overrides `inheritedMethod' answer whether some subclass method that > // differs in erasure from overridingMethod could override `inheritedMethod' >@@ -147,6 +71,11 @@ > void checkConcreteInheritedMethod(MethodBinding concreteMethod, MethodBinding[] abstractMethods) { > super.checkConcreteInheritedMethod(concreteMethod, abstractMethods); > boolean analyseNullAnnotations = this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled; >+ // TODO (stephan): unclear if this srcMethod is actually needed >+ AbstractMethodDeclaration srcMethod = null; >+ if (analyseNullAnnotations && this.type.equals(concreteMethod.declaringClass)) // is currentMethod from the current type? >+ srcMethod = concreteMethod.sourceMethod(); >+ boolean hasNonNullDefault = concreteMethod.hasNonNullDefault(); > for (int i = 0, l = abstractMethods.length; i < l; i++) { > MethodBinding abstractMethod = abstractMethods[i]; > if (concreteMethod.isVarargs() != abstractMethod.isVarargs()) >@@ -167,8 +96,9 @@ > || this.type.superclass.erasure().findSuperTypeOriginatingFrom(originalInherited.declaringClass) == null) > this.type.addSyntheticBridgeMethod(originalInherited, concreteMethod.original()); > } >- if (analyseNullAnnotations && !concreteMethod.isStatic() && !abstractMethod.isStatic()) >- checkNullSpecInheritance(concreteMethod, abstractMethod); >+ if (analyseNullAnnotations && !concreteMethod.isStatic() && !abstractMethod.isStatic()) { >+ checkNullSpecInheritance(concreteMethod, srcMethod, hasNonNullDefault, true, abstractMethod, this.type.scope); >+ } > } > } > void checkForBridgeMethod(MethodBinding currentMethod, MethodBinding inheritedMethod, MethodBinding[] allInheritedMethods) { >@@ -369,100 +299,37 @@ > void checkAgainstInheritedMethods(MethodBinding currentMethod, MethodBinding[] methods, int length, MethodBinding[] allInheritedMethods) > { > super.checkAgainstInheritedMethods(currentMethod, methods, length, allInheritedMethods); >- if (this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled) { >+ CompilerOptions options = this.environment.globalOptions; >+ if (options.isAnnotationBasedNullAnalysisEnabled >+ && (currentMethod.tagBits & TagBits.IsNullnessKnown) == 0) >+ { >+ // if annotations are inherited these have been checked during STB.resolveTypesFor() (for methods explicit in this.type) >+ AbstractMethodDeclaration srcMethod = null; >+ if (this.type.equals(currentMethod.declaringClass)) // is currentMethod from the current type? >+ srcMethod = currentMethod.sourceMethod(); >+ boolean hasNonNullDefault = currentMethod.hasNonNullDefault(); > for (int i = length; --i >= 0;) > if (!currentMethod.isStatic() && !methods[i].isStatic()) >- checkNullSpecInheritance(currentMethod, methods[i]); >+ checkNullSpecInheritance(currentMethod, srcMethod, hasNonNullDefault, true, methods[i], this.type.scope); > } > } > >-void checkNullSpecInheritance(MethodBinding currentMethod, MethodBinding inheritedMethod) { >- // precondition: caller has checked whether annotation-based null analysis is enabled. >- long inheritedBits = inheritedMethod.tagBits; >- long currentBits = currentMethod.tagBits; >- AbstractMethodDeclaration srcMethod = null; >- if (this.type.equals(currentMethod.declaringClass)) // is currentMethod from the current type? >- srcMethod = currentMethod.sourceMethod(); >- >- // return type: >- if ((inheritedBits & TagBits.AnnotationNonNull) != 0) { >- long currentNullBits = currentBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable); >- if (currentNullBits != TagBits.AnnotationNonNull) { >- if (srcMethod != null) { >- this.type.scope.problemReporter().illegalReturnRedefinition(srcMethod, inheritedMethod, >- this.environment.getNonNullAnnotationName()); >- } else { >- this.type.scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); >- return; >- } >- } >+void checkNullSpecInheritance(MethodBinding currentMethod, AbstractMethodDeclaration srcMethod, >+ boolean hasNonNullDefault, boolean complain, MethodBinding inheritedMethod, Scope scope) >+{ >+ complain &= !currentMethod.isConstructor(); >+ if (!hasNonNullDefault && !complain && !this.environment.globalOptions.inheritNullAnnotations) { >+ // nothing to be done, take the shortcut >+ currentMethod.tagBits |= TagBits.IsNullnessKnown; >+ return; > } >- >- // parameters: >- Argument[] currentArguments = srcMethod == null ? null : srcMethod.arguments; >- if (inheritedMethod.parameterNonNullness != null) { >- // inherited method has null-annotations, check compatibility: >- >- int length = inheritedMethod.parameterNonNullness.length; >- for (int i = 0; i < length; i++) { >- Argument currentArgument = currentArguments == null ? null : currentArguments[i]; >- >- Boolean inheritedNonNullNess = inheritedMethod.parameterNonNullness[i]; >- Boolean currentNonNullNess = (currentMethod.parameterNonNullness == null) >- ? null : currentMethod.parameterNonNullness[i]; >- if (inheritedNonNullNess != null) { // super has a null annotation >- if (currentNonNullNess == null) { // current parameter lacks null annotation >- boolean needNonNull = false; >- char[][] annotationName; >- if (inheritedNonNullNess == Boolean.TRUE) { >- needNonNull = true; >- annotationName = this.environment.getNonNullAnnotationName(); >- } else { >- annotationName = this.environment.getNullableAnnotationName(); >- } >- if (currentArgument != null) { >- this.type.scope.problemReporter().parameterLackingNullAnnotation( >- currentArgument, >- inheritedMethod.declaringClass, >- needNonNull, >- annotationName); >- continue; >- } else { >- this.type.scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); >- break; >- } >- } >- } >- if (inheritedNonNullNess != Boolean.TRUE) { // super parameter is not restricted to @NonNull >- if (currentNonNullNess == Boolean.TRUE) { // current parameter is restricted to @NonNull >- if (currentArgument != null) >- this.type.scope.problemReporter().illegalRedefinitionToNonNullParameter( >- currentArgument, >- inheritedMethod.declaringClass, >- inheritedNonNullNess == null >- ? null >- : this.environment.getNullableAnnotationName()); >- else >- this.type.scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); >- } >- } >- } >- } else if (currentMethod.parameterNonNullness != null) { >- // super method has no annotations but current has >- for (int i = 0; i < currentMethod.parameterNonNullness.length; i++) { >- if (currentMethod.parameterNonNullness[i] == Boolean.TRUE) { // tightening from unconstrained to @NonNull >- if (currentArguments != null) { >- this.type.scope.problemReporter().illegalRedefinitionToNonNullParameter( >- currentArguments[i], >- inheritedMethod.declaringClass, >- null); >- } else { >- this.type.scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); >- break; >- } >- } >- } >+ // in this context currentMethod can be inherited, too. Recurse if needed. >+ if (currentMethod.declaringClass != this.type >+ && (currentMethod.tagBits & TagBits.IsNullnessKnown) == 0) >+ { >+ this.buddyImplicitNullAnnotationsVerifier.checkImplicitNullAnnotations(currentMethod, srcMethod, complain, this.type.scope); > } >+ super.checkNullSpecInheritance(currentMethod, srcMethod, hasNonNullDefault, complain, inheritedMethod, scope); > } > > void reportRawReferences() { >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java >index c38d541..a0db1e9 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java >@@ -973,6 +973,23 @@ > return false; > } > >+/** >+ * Answer whether a @NonNullByDefault is applicable at the given method binding. >+ */ >+boolean hasNonNullDefault() { >+ // Note, STB overrides for correctly handling local types >+ ReferenceBinding currentType = this; >+ while (currentType != null) { >+ if ((currentType.tagBits & TagBits.AnnotationNonNullByDefault) != 0) >+ return true; >+ if ((currentType.tagBits & TagBits.AnnotationNullUnspecifiedByDefault) != 0) >+ return false; >+ currentType = currentType.enclosingType(); >+ } >+ // package >+ return this.getPackage().defaultNullness == NONNULL_BY_DEFAULT; >+} >+ > public final boolean hasRestrictedAccess() { > return (this.modifiers & ExtraCompilerModifiers.AccRestrictedAccess) != 0; > } >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java >index b6ec8bf..9c9c197 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java >@@ -36,6 +36,7 @@ > import org.eclipse.jdt.internal.compiler.ast.TypeParameter; > import org.eclipse.jdt.internal.compiler.ast.TypeReference; > import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; >+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; > import org.eclipse.jdt.internal.compiler.impl.Constant; > import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; > import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable; >@@ -1114,6 +1115,18 @@ > this.scope.buildMethods(); > } > >+private void initializeNullDefault() { >+ // ensure nullness defaults are initialized at all enclosing levels: >+ switch (this.nullnessDefaultInitialized) { >+ case 0: >+ getAnnotationTagBits(); // initialize >+ //$FALL-THROUGH$ >+ case 1: >+ getPackage().isViewedAsDeprecated(); // initialize annotations >+ this.nullnessDefaultInitialized = 2; >+ } >+} >+ > /** > * Returns true if a type is identical to another one, > * or for generic types, true if compared to its raw type. >@@ -1616,30 +1629,26 @@ > typeParameters[i].binding = null; > return null; > } >- if (this.scope.compilerOptions().isAnnotationBasedNullAnalysisEnabled) >- createArgumentBindings(method); // need annotations resolved already at this point >+ CompilerOptions compilerOptions = this.scope.compilerOptions(); >+ if (compilerOptions.isAnnotationBasedNullAnalysisEnabled) { >+ createArgumentBindings(method, compilerOptions); // need annotations resolved already at this point >+ } > if (foundReturnTypeProblem) > return method; // but its still unresolved with a null return type & is still connected to its method declaration > > method.modifiers &= ~ExtraCompilerModifiers.AccUnresolved; > return method; > } >-private void createArgumentBindings(MethodBinding method) { >- // ensure nullness defaults are initialized at all enclosing levels: >- switch (this.nullnessDefaultInitialized) { >- case 0: >- getAnnotationTagBits(); // initialize >- //$FALL-THROUGH$ >- case 1: >- getPackage().isViewedAsDeprecated(); // initialize annotations >- this.nullnessDefaultInitialized = 2; >- } >+private void createArgumentBindings(MethodBinding method, CompilerOptions compilerOptions) { >+ initializeNullDefault(); > AbstractMethodDeclaration methodDecl = method.sourceMethod(); > if (methodDecl != null) { >+ // while creating argument bindings we also collect explicit null annotations: > if (method.parameters != Binding.NO_PARAMETERS) > methodDecl.createArgumentBindings(); >- if ((findNonNullDefault(methodDecl.scope, methodDecl.scope.environment()) == NONNULL_BY_DEFAULT)) { >- method.fillInDefaultNonNullness(); >+ // add implicit annotations (inherited(?) & default): >+ if (compilerOptions.isAnnotationBasedNullAnalysisEnabled) { >+ new ImplicitNullAnnotationVerifier(compilerOptions.inheritNullAnnotations).checkImplicitNullAnnotations(method, methodDecl, true, this.scope); > } > } > } >@@ -1711,16 +1720,11 @@ > return true; > } > >-/** >- * Answer the nullness default applicable at the given method binding. >- * Possible values: {@link Binding#NO_NULL_DEFAULT}, {@link Binding#NULL_UNSPECIFIED_BY_DEFAULT}, {@link Binding#NONNULL_BY_DEFAULT}. >- * @param currentScope where to start search for lexically enclosing default >- * @param environment gateway to options >- */ >-private int findNonNullDefault(Scope currentScope, LookupEnvironment environment) { >+boolean hasNonNullDefault() { > // find the applicable default inside->out: > > SourceTypeBinding currentType = null; >+ Scope currentScope = this.scope; > while (currentScope != null) { > switch (currentScope.kind) { > case Scope.METHOD_SCOPE: >@@ -1728,9 +1732,9 @@ > if (referenceMethod != null && referenceMethod.binding != null) { > long methodTagBits = referenceMethod.binding.tagBits; > if ((methodTagBits & TagBits.AnnotationNonNullByDefault) != 0) >- return NONNULL_BY_DEFAULT; >+ return true; > if ((methodTagBits & TagBits.AnnotationNullUnspecifiedByDefault) != 0) >- return NULL_UNSPECIFIED_BY_DEFAULT; >+ return false; > } > break; > case Scope.CLASS_SCOPE: >@@ -1738,7 +1742,7 @@ > if (currentType != null) { > int foundDefaultNullness = currentType.defaultNullness; > if (foundDefaultNullness != NO_NULL_DEFAULT) { >- return foundDefaultNullness; >+ return foundDefaultNullness == NONNULL_BY_DEFAULT; > } > } > break; >@@ -1748,13 +1752,10 @@ > > // package > if (currentType != null) { >- int foundDefaultNullness = currentType.getPackage().defaultNullness; >- if (foundDefaultNullness != NO_NULL_DEFAULT) { >- return foundDefaultNullness; >- } >+ return currentType.getPackage().defaultNullness == NONNULL_BY_DEFAULT; > } > >- return NO_NULL_DEFAULT; >+ return false; > } > > public AnnotationHolder retrieveAnnotationHolder(Binding binding, boolean forceInitialization) { >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TagBits.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TagBits.java >index 21b1c11..cd25508 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TagBits.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TagBits.java >@@ -61,6 +61,9 @@ > long MultiCatchParameter = ASTNode.Bit13; // local > long IsResource = ASTNode.Bit14; // local > >+ // have implicit null annotations been collected (inherited(?) & default)? >+ long IsNullnessKnown = ASTNode.Bit13; // method >+ > // test bits to see if parts of binary types are faulted > long AreFieldsSorted = ASTNode.Bit13; > long AreFieldsComplete = ASTNode.Bit14; // sorted and all resolved >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java >index b527025..c2d8464 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java >@@ -318,6 +318,7 @@ > case IProblem.ParameterLackingNonNullAnnotation: > case IProblem.ParameterLackingNullableAnnotation: > case IProblem.CannotImplementIncompatibleNullness: >+ case IProblem.ConflictingNullAnnotations: > return CompilerOptions.NullSpecViolation; > > case IProblem.RequiredNonNullButProvidedPotentialNull: >@@ -8418,6 +8419,23 @@ > this.handle(IProblem.ContradictoryNullAnnotations, arguments, shortArguments, annotation.sourceStart, annotation.sourceEnd); > } > >+public void conflictingNullAnnotations(MethodBinding currentMethod, ASTNode location, MethodBinding inheritedMethod) >+{ >+ char[][] nonNullAnnotationName = this.options.nonNullAnnotationName; >+ char[][] nullableAnnotationName = this.options.nullableAnnotationName; >+ String[] arguments = { >+ new String(CharOperation.concatWith(nonNullAnnotationName, '.')), >+ new String(CharOperation.concatWith(nullableAnnotationName, '.')), >+ new String(inheritedMethod.declaringClass.readableName()) >+ }; >+ String[] shortArguments = { >+ new String(nonNullAnnotationName[nonNullAnnotationName.length-1]), >+ new String(nullableAnnotationName[nullableAnnotationName.length-1]), >+ new String(inheritedMethod.declaringClass.shortReadableName()) >+ }; >+ this.handle(IProblem.ConflictingNullAnnotations, arguments, shortArguments, location.sourceStart, location.sourceEnd); >+} >+ > public void illegalAnnotationForBaseType(TypeReference type, Annotation[] annotations, char[] annotationName, long nullAnnotationTagBit) > { > int typeId = (nullAnnotationTagBit == TagBits.AnnotationNullable) >diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties >index bfa5dc4..45abcb7 100644 >--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties >+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties >@@ -690,6 +690,7 @@ > 931 = Redundant null check: The variable {0} is specified as @{1} > 932 = Null comparison always yields false: The variable {0} is specified as @{1} > 933 = Null type mismatch: required ''@{0} {1}'' but the provided value is specified as @{2} >+939 = The default ''@{0}'' conflicts with the inherited ''@{1}'' annotation in the overridden method from {2} > > ### ELABORATIONS > ## Access restrictions >diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java >index 8130699..36279d2 100644 >--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java >+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java >@@ -95,6 +95,7 @@ > * COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO > * COMPILER_PB_MISSING_ENUM_CASE_DESPITE_DEFAULT > * COMPILER_PB_SWITCH_MISSING_DEFAULT_CASE >+ * COMPILER_INHERIT_NULL_ANNOTATIONS > *******************************************************************************/ > > package org.eclipse.jdt.core; >@@ -1671,6 +1672,25 @@ > */ > public static final String COMPILER_PB_REDUNDANT_NULL_ANNOTATION = PLUGIN_ID + ".compiler.problem.redundantNullAnnotation"; //$NON-NLS-1$ > /** >+ * Compiler option ID: Inheritance of null annotations. >+ * <p>When enabled, the compiler will check for each method without any explicit null annotations: >+ * If it overrides a method which has null annotations, it will treat the >+ * current method as if it had the same annotations as the overridden method.</p> >+ * <p>Annotation inheritance will use the <em>effective</em> nullness of the overridden method >+ * after transitively applying inheritance and after applying any default nullness >+ * (see {@link #COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME}) at the site of the overridden method.</p> >+ * <p>Annotation inheritance has precedence over a nullness default, i.e., a nullness default at the site >+ * of the overriding method will never override an inherited nullness annotation.</p> >+ * <dl> >+ * <dt>Option id:</dt><dd><code>"org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations"</code></dd> >+ * <dt>Possible values:</dt><dd><code>{ "disabled", "enabled" }</code></dd> >+ * <dt>Default:</dt><dd><code>"disabled"</code></dd> >+ * </dl> >+ * @since 3.9 >+ * @category CompilerOptionID >+ */ >+ public static final String COMPILER_INHERIT_NULL_ANNOTATIONS = JavaCore.PLUGIN_ID+".compiler.annotation.inheritNullAnnotations"; //$NON-NLS-1$ >+ /** > * Compiler option ID: Setting Source Compatibility Mode. > * <p>Specify whether which source level compatibility is used. From 1.4 on, <code>'assert'</code> is a keyword > * reserved for assertion support. Also note, than when toggling to 1.4 mode, the target VM
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 388281
: 220779 |
220780