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 48667a7..bb67d89 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
@@ -1799,6 +1799,11 @@
" \n" +
" \n" +
" \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
" \n" +
" \n" +
" \n" +
@@ -1857,14 +1862,18 @@
" \n" +
" \n" +
" \n" +
- " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
+ " \n" +
" \n" +
" \n" +
+ " \n" +
" \n" +
" \n" +
" \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 9f0995d..9e23bb3 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
@@ -380,6 +380,7 @@
expectedProblemAttributes.put("CannotDefineStaticInitializerInLocalType", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL));
expectedProblemAttributes.put("CannotExtendEnum", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("CannotHideAnInstanceMethodWithAStaticMethod", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
+ expectedProblemAttributes.put("CannotImplementIncompatibleNullness", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("CannotImportPackage", new ProblemAttributes(CategorizedProblem.CAT_IMPORT));
expectedProblemAttributes.put("CannotInferElidedTypes", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("CannotInvokeSuperConstructorInEnum", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
@@ -472,6 +473,7 @@
expectedProblemAttributes.put("IllegalAccessFromTypeVariable", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("IllegalCast", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("IllegalClassLiteralForTypeVariable", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
+ expectedProblemAttributes.put("IllegalDefinitionToNonNullParameter", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("IllegalDimension", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL));
expectedProblemAttributes.put("IllegalEnclosingInstanceSpecification", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("IllegalExtendedDimensions", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
@@ -506,6 +508,8 @@
expectedProblemAttributes.put("IllegalPrimitiveOrArrayTypeForEnclosingInstance", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("IllegalQualifiedEnumConstantLabel", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
expectedProblemAttributes.put("IllegalQualifiedParameterizedTypeAllocation", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
+ expectedProblemAttributes.put("IllegalReturnNullityRedefinition", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
+ expectedProblemAttributes.put("IllegalRedefinitionToNonNullParameter", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("IllegalStaticModifierForMemberType", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("IllegalTypeVariableSuperReference", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL));
expectedProblemAttributes.put("IllegalUnderscorePosition", new ProblemAttributes(CategorizedProblem.CAT_SYNTAX));
@@ -690,6 +694,7 @@
expectedProblemAttributes.put("MissingEnclosingInstanceForConstructorCall", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("MissingEnumConstantCase", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("MissingOverrideAnnotation", new ProblemAttributes(CategorizedProblem.CAT_CODE_STYLE));
+ expectedProblemAttributes.put("MissingNullAnnotationType", new ProblemAttributes(CategorizedProblem.CAT_BUILDPATH));
expectedProblemAttributes.put("MissingOverrideAnnotationForInterfaceMethodImplementation", new ProblemAttributes(CategorizedProblem.CAT_CODE_STYLE));
expectedProblemAttributes.put("MissingReturnType", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("MissingSemiColon", new ProblemAttributes(CategorizedProblem.CAT_SYNTAX));
@@ -729,6 +734,7 @@
expectedProblemAttributes.put("NotVisibleField", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
expectedProblemAttributes.put("NotVisibleMethod", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
expectedProblemAttributes.put("NotVisibleType", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
+ expectedProblemAttributes.put("NullAnnotationNameMustBeQualified", new ProblemAttributes(CategorizedProblem.CAT_BUILDPATH));
expectedProblemAttributes.put("NullLocalVariableComparisonYieldsFalse", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("NullLocalVariableInstanceofYieldsFalse", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("NullLocalVariableReference", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
@@ -745,6 +751,8 @@
expectedProblemAttributes.put("PackageCollidesWithType", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("PackageIsNotExpectedPackage", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL));
expectedProblemAttributes.put("ParameterAssignment", new ProblemAttributes(CategorizedProblem.CAT_CODE_STYLE));
+ expectedProblemAttributes.put("ParameterLackingNonNullAnnotation", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
+ expectedProblemAttributes.put("ParameterLackingNullableAnnotation", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("ParameterMismatch", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
expectedProblemAttributes.put("ParameterizedConstructorArgumentTypeMismatch", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("ParameterizedMethodArgumentTypeMismatch", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
@@ -771,6 +779,7 @@
expectedProblemAttributes.put("PotentiallyUnclosedCloseable", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("PotentiallyUnclosedCloseableAtExit", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("PotentialNullLocalVariableReference", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
+ expectedProblemAttributes.put("PotentialNullMessageSendReference", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("PublicClassMustMatchFileName", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("RawMemberTypeCannotBeParameterized", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("RawTypeReference", new ProblemAttributes(CategorizedProblem.CAT_UNCHECKED_RAW));
@@ -779,10 +788,15 @@
expectedProblemAttributes.put("RedefinedLocal", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL));
expectedProblemAttributes.put("RedundantSpecificationOfTypeArguments", new ProblemAttributes(CategorizedProblem.CAT_UNNECESSARY_CODE));
expectedProblemAttributes.put("RedundantLocalVariableNullAssignment", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
+ expectedProblemAttributes.put("RedundantNullAnnotation", new ProblemAttributes(CategorizedProblem.CAT_UNNECESSARY_CODE));
expectedProblemAttributes.put("RedundantNullCheckOnNonNullLocalVariable", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
+ expectedProblemAttributes.put("RedundantNullCheckOnNonNullMessageSend", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("RedundantNullCheckOnNullLocalVariable", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("RedundantSuperinterface", new ProblemAttributes(CategorizedProblem.CAT_UNNECESSARY_CODE));
expectedProblemAttributes.put("ReferenceToForwardField", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
+ expectedProblemAttributes.put("RequiredNonNullButProvidedNull", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
+ expectedProblemAttributes.put("RequiredNonNullButProvidedPotentialNull", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
+ expectedProblemAttributes.put("RequiredNonNullButProvidedUnknown", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("ReferenceToForwardTypeVariable", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("ResourceHasToImplementAutoCloseable", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("ReturnTypeAmbiguous", DEPRECATED);
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
new file mode 100644
index 0000000..7d8ef09
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java
@@ -0,0 +1,2143 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 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.core.tests.compiler.regression;
+
+
+import java.io.File;
+import java.util.Map;
+
+import junit.framework.Test;
+
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+
+public class NullAnnotationTest extends AbstractComparableTest {
+
+// class libraries including our default null annotation types:
+String[] LIBS;
+
+// names and content of custom annotations used in a few tests:
+private static final String CUSTOM_NONNULL_NAME = "org/foo/NonNull.java";
+private static final String CUSTOM_NONNULL_CONTENT =
+ "package org.foo;\n" +
+ "import static java.lang.annotation.ElementType.*;\n" +
+ "import java.lang.annotation.*;\n" +
+ "@Retention(RetentionPolicy.CLASS)\n" +
+ "@Target({METHOD,PARAMETER,LOCAL_VARIABLE})\n" +
+ "public @interface NonNull {\n" +
+ "}\n";
+private static final String CUSTOM_NULLABLE_NAME = "org/foo/Nullable.java";
+private static final String CUSTOM_NULLABLE_CONTENT = "package org.foo;\n" +
+ "import static java.lang.annotation.ElementType.*;\n" +
+ "import java.lang.annotation.*;\n" +
+ "@Retention(RetentionPolicy.CLASS)\n" +
+ "@Target({METHOD,PARAMETER,LOCAL_VARIABLE})\n" +
+ "public @interface Nullable {\n" +
+ "}\n";
+
+public NullAnnotationTest(String name) {
+ super(name);
+}
+
+// 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[] { "test_nonnull_var_in_constrol_structure_3" };
+// TESTS_NUMBERS = new int[] { 561 };
+// TESTS_RANGE = new int[] { 1, 2049 };
+}
+
+public static Test suite() {
+ return buildComparableTestSuite(testClass());
+}
+
+public static Class testClass() {
+ return NullAnnotationTest.class;
+}
+
+protected void setUp() throws Exception {
+ super.setUp();
+ if (this.LIBS == null) {
+ String[] defaultLibs = getDefaultClassPaths();
+ int len = defaultLibs.length;
+ this.LIBS = new String[len+1];
+ System.arraycopy(defaultLibs, 0, this.LIBS, 0, len);
+ File bundleFile = FileLocator.getBundleFile(Platform.getBundle("org.eclipse.jdt.annotation.null"));
+ if (bundleFile.isDirectory())
+ this.LIBS[len] = bundleFile.getPath()+"/bin";
+ else
+ this.LIBS[len] = bundleFile.getPath();
+ }
+}
+// Conditionally augment problem detection settings
+static boolean setNullRelatedOptions = true;
+protected Map getCompilerOptions() {
+ Map defaultOptions = super.getCompilerOptions();
+ if (setNullRelatedOptions) {
+ defaultOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ defaultOptions.put(CompilerOptions.OPTION_ReportPotentialNullReference, CompilerOptions.ERROR);
+ defaultOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.ERROR);
+ defaultOptions.put(CompilerOptions.OPTION_ReportRawTypeReference, CompilerOptions.IGNORE);
+ defaultOptions.put(CompilerOptions.OPTION_IncludeNullInfoFromAsserts, CompilerOptions.ENABLED);
+
+ defaultOptions.put(CompilerOptions.OPTION_ReportMissingOverrideAnnotationForInterfaceMethodImplementation, CompilerOptions.DISABLED);
+
+ // enable null annotations:
+ defaultOptions.put(CompilerOptions.OPTION_AnnotationBasedNullAnalysis, CompilerOptions.ENABLED);
+ // leave other new options at these defaults:
+// defaultOptions.put(CompilerOptions.OPTION_ReportNullContractViolation, CompilerOptions.ERROR);
+// defaultOptions.put(CompilerOptions.OPTION_ReportPotentialNullContractViolation, CompilerOptions.ERROR);
+// defaultOptions.put(CompilerOptions.OPTION_ReportNullContractInsufficientInfo, CompilerOptions.WARNING);
+
+// defaultOptions.put(CompilerOptions.OPTION_NullableAnnotationName, "org.eclipse.jdt.annotation.Nullable");
+// defaultOptions.put(CompilerOptions.OPTION_NonNullAnnotationName, "org.eclipse.jdt.annotation.NonNull");
+ }
+ return defaultOptions;
+}
+void runNegativeTestWithLibs(String[] testFiles, String expectedErrorLog) {
+ runNegativeTest(
+ testFiles,
+ expectedErrorLog,
+ this.LIBS,
+ false /*shouldFlush*/);
+}
+void runNegativeTestWithLibs(boolean shouldFlushOutputDirectory, String[] testFiles, Map customOptions, String expectedErrorLog) {
+ runNegativeTest(
+ shouldFlushOutputDirectory,
+ testFiles,
+ this.LIBS,
+ customOptions,
+ expectedErrorLog,
+ // runtime options
+ JavacTestOptions.Excuse.EclipseWarningConfiguredAsError);
+}
+void runNegativeTestWithLibs(String[] testFiles, Map customOptions, String expectedErrorLog) {
+ runNegativeTestWithLibs(false /* flush output directory */, testFiles, customOptions, expectedErrorLog);
+}
+void runConformTestWithLibs(String[] testFiles, Map customOptions, String expectedCompilerLog) {
+ runConformTestWithLibs(false /* flush output directory */, testFiles, customOptions, expectedCompilerLog);
+}
+void runConformTestWithLibs(boolean shouldFlushOutputDirectory, String[] testFiles, Map customOptions, String expectedCompilerLog) {
+ runConformTest(
+ shouldFlushOutputDirectory,
+ testFiles,
+ this.LIBS,
+ customOptions,
+ expectedCompilerLog,
+ "",/* expected output */
+ "",/* expected error */
+ JavacTestOptions.Excuse.EclipseWarningConfiguredAsError);
+}
+void runConformTest(String[] testFiles, Map customOptions, String expectedOutputString) {
+ runConformTest(
+ testFiles,
+ expectedOutputString,
+ null /*classLibraries*/,
+ true /*shouldFlushOutputDirectory*/,
+ null /*vmArguments*/,
+ customOptions,
+ null /*customRequestor*/);
+
+}
+// a nullable argument is dereferenced without a check
+public void test_nullable_paramter_001() {
+ runNegativeTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(@Nullable Object o) {\n" +
+ " System.out.print(o.toString());\n" +
+ " }\n" +
+ "}\n"},
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " System.out.print(o.toString());\n" +
+ " ^\n" +
+ "Potential null pointer access: The variable o may be null at this location\n" +
+ "----------\n",
+ this.LIBS,
+ true /* shouldFlush*/);
+}
+
+// a null value is passed to a nullable argument
+public void test_nullable_paramter_002() {
+ runConformTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(@Nullable Object o) {\n" +
+ " // nop\n" +
+ " }\n" +
+ " void bar() {\n" +
+ " foo(null);\n" +
+ " }\n" +
+ "}\n"},
+ "",
+ this.LIBS,
+ false/*shouldFlush*/,
+ null/*vmArgs*/);
+}
+
+// a non-null argument is checked for null
+public void test_nonnull_parameter_001() {
+ runNegativeTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(@NonNull Object o) {\n" +
+ " if (o != null)\n" +
+ " System.out.print(o.toString());\n" +
+ " }\n" +
+ "}\n"},
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " if (o != null)\n" +
+ " ^\n" +
+ "Redundant null check: The variable o cannot be null at this location\n" +
+ "----------\n",
+ this.LIBS,
+ true /* shouldFlush*/);
+}
+// a non-null argument is dereferenced without a check
+public void test_nonnull_parameter_002() {
+ runConformTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(@NonNull Object o) {\n" +
+ " System.out.print(o.toString());\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(\"OK\");\n" +
+ " }\n" +
+ "}\n"},
+ "OK",
+ this.LIBS,
+ false/*shouldFlush*/,
+ null/*vmArgs*/);
+}
+// passing null to nonnull parameter - many fields in enclosing class
+public void test_nonnull_parameter_003() {
+ runNegativeTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " int i00, i01, i02, i03, i04, i05, i06, i07, i08, i09;" +
+ " int i10, i11, i12, i13, i14, i15, i16, i17, i18, i19;" +
+ " int i20, i21, i22, i23, i24, i25, i26, i27, i28, i29;" +
+ " int i30, i31, i32, i33, i34, i35, i36, i37, i38, i39;" +
+ " int i40, i41, i42, i43, i44, i45, i46, i47, i48, i49;" +
+ " int i50, i51, i52, i53, i54, i55, i56, i57, i58, i59;" +
+ " int i60, i61, i62, i63, i64, i65, i66, i67, i68, i69;" +
+ " void foo(@NonNull Object o) {\n" +
+ " System.out.print(o.toString());\n" +
+ " }\n" +
+ " void bar() {\n" +
+ " foo(null);\n" +
+ " }\n" +
+ "}\n"},
+ "----------\n" +
+ "1. ERROR in X.java (at line 7)\n" +
+ " foo(null);\n" +
+ " ^^^^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" +
+ "----------\n",
+ this.LIBS,
+ true /* shouldFlush*/);
+}
+// passing potential null to nonnull parameter - target method is consumed from .class
+public void test_nonnull_parameter_004() {
+ runConformTestWithLibs(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " void setObject(@NonNull Object o) { }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "public class X {\n" +
+ " void bar(Lib l, boolean b) {\n" +
+ " Object o = null;\n" +
+ " if (b) o = new Object();\n" +
+ " l.setObject(o);\n" +
+ " }\n" +
+ "}\n"},
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 5)\n" +
+ " l.setObject(o);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" +
+ "----------\n");
+}
+// passing unknown value to nonnull parameter - target method is consumed from .class
+public void test_nonnull_parameter_005() {
+ runConformTestWithLibs(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " void setObject(@NonNull Object o) { }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+ runConformTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "public class X {\n" +
+ " void bar(Lib l, Object o) {\n" +
+ " l.setObject(o);\n" +
+ " }\n" +
+ "}\n"},
+ null /* options */,
+ "----------\n" +
+ "1. WARNING in X.java (at line 3)\n" +
+ " l.setObject(o);\n" +
+ " ^\n" +
+ "Potential type mismatch: required \'@NonNull Object\' but nullness of the provided value is unknown\n" +
+ "----------\n");
+}
+// a ternary non-null expression is passed to a nonnull parameter
+public void test_nonnull_parameter_006() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void m1(@NonNull String a) {}\n" +
+ " void m2(@Nullable String b) {\n" +
+ " m1(b == null ? \"\" : b);\n" +
+ " }\n" +
+ "}\n"},
+ customOptions,
+ "" /* compiler output */);
+}
+// nullable value passed to a non-null parameter in a super-call
+public void test_nonnull_parameter_007() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "XSub.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class XSub extends XSuper {\n" +
+ " XSub(@Nullable String b) {\n" +
+ " super(b);\n" +
+ " }\n" +
+ "}\n",
+ "XSuper.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class XSuper {\n" +
+ " XSuper(@NonNull String b) {\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in XSub.java (at line 4)\n" +
+ " super(b);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n");
+}
+// a nullable value is passed to a non-null parameter in an allocation expression
+public void test_nonnull_parameter_008() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " X(@NonNull String a) {}\n" +
+ " static X create(@Nullable String b) {\n" +
+ " return new X(b);\n" +
+ " }\n" +
+ "}\n"},
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 5)\n" +
+ " return new X(b);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n" /* compiler output */);
+}
+// a nullable value is passed to a non-null parameter in a qualified allocation expression
+public void test_nonnull_parameter_009() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " class Local {\n" +
+ " Local(@NonNull String a) {}\n" +
+ " }\n" +
+ " Local create(@Nullable String b) {\n" +
+ " return this.new Local(b);\n" +
+ " }\n" +
+ "}\n"},
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 7)\n" +
+ " return this.new Local(b);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n" /* compiler output */);
+}
+// assigning potential null to a nonnull local variable
+public void test_nonnull_local_001() {
+ runNegativeTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(boolean b, Object p) {\n" +
+ " @NonNull Object o1 = b ? null : new Object();\n" +
+ " @NonNull String o2 = \"\";\n" +
+ " o2 = null;\n" +
+ " @NonNull Object o3 = p;\n" +
+ " }\n" +
+ "}\n"},
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " @NonNull Object o1 = b ? null : new Object();\n" +
+ " ^^^^^^^^^^^^^^^^^^^^^^^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" +
+ "----------\n" +
+ "2. ERROR in X.java (at line 6)\n" +
+ " o2 = null;\n" +
+ " ^^^^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value is null\n" +
+ "----------\n" +
+ "3. WARNING in X.java (at line 7)\n" +
+ " @NonNull Object o3 = p;\n" +
+ " ^\n" +
+ "Potential type mismatch: required \'@NonNull Object\' but nullness of the provided value is unknown\n" +
+ "----------\n",
+ this.LIBS,
+ true /* shouldFlush*/);
+}
+
+// a method tries to tighten the type specification, super declares parameter o as @Nullable
+// other parameters: s is redefined from not constrained to @Nullable which is OK
+// third is redefined from not constrained to @NonNull which is bad, too
+public void test_parameter_specification_inheritance_001() {
+ runConformTestWithLibs(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " void foo(String s, @Nullable Object o, Object third) { }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X extends Lib {\n" +
+ " @Override\n" +
+ " void foo(@Nullable String s, @NonNull Object o, @NonNull Object third) { System.out.print(o.toString()); }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " void foo(@Nullable String s, @NonNull Object o, @NonNull Object third) { System.out.print(o.toString()); }\n" +
+ " ^^^^^^^^^^^^^^^\n" +
+ "Illegal redefinition of parameter o, inherited method from Lib declares this parameter as @Nullable\n" +
+ "----------\n" +
+ "2. ERROR in X.java (at line 4)\n" +
+ " void foo(@Nullable String s, @NonNull Object o, @NonNull Object third) { System.out.print(o.toString()); }\n" +
+ " ^^^^^^^^^^^^^^^\n" +
+ "Illegal redefinition of parameter third, inherited method from Lib does not constrain this parameter\n" +
+ "----------\n");
+}
+// a method body fails to redeclare the inherited null annotation, super declares parameter as @Nullable
+public void test_parameter_specification_inheritance_002() {
+ runConformTest(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " void foo(@Nullable Object o) { }\n" +
+ "}\n"
+ },
+ "",
+ this.LIBS,
+ false/*shouldFlush*/,
+ null/*vmArgs*/);
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "public class X extends Lib {\n" +
+ " @Override\n" +
+ " void foo(Object o) {\n" +
+ " System.out.print(o.toString());\n" +
+ " }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 3)\n" +
+ " void foo(Object o) {\n" +
+ " ^^^^^^\n" +
+ "Missing null annotation: inherited method from Lib declares this parameter as @Nullable\n" +
+ "----------\n");
+}
+// a method relaxes the parameter null specification, super interface declares parameter o as @NonNull
+// other (first) parameter just repeats the inherited @NonNull
+public void test_parameter_specification_inheritance_003() {
+ runConformTest(
+ new String[] {
+ "IX.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public interface IX {\n" +
+ " void foo(@NonNull String s, @NonNull Object o);\n" +
+ "}\n",
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X implements IX {\n" +
+ " public void foo(@NonNull String s, @Nullable Object o) { ; }\n" +
+ " void bar() { foo(\"OK\", null); }\n" +
+ "}\n"
+ },
+ "",
+ this.LIBS,
+ false/*shouldFlush*/,
+ null/*vmArgs*/);
+}
+// a method adds a @NonNull annotation, super interface has no null annotation
+// changing other from unconstrained to @Nullable is OK
+public void test_parameter_specification_inheritance_004() {
+ runConformTest(
+ new String[] {
+ "IX.java",
+ "public interface IX {\n" +
+ " void foo(Object o, Object other);\n" +
+ "}\n"
+ });
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X implements IX {\n" +
+ " public void foo(@NonNull Object o, @Nullable Object other) { System.out.print(o.toString()); }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 3)\n" +
+ " public void foo(@NonNull Object o, @Nullable Object other) { System.out.print(o.toString()); }\n" +
+ " ^^^^^^^^^^^^^^^\n" +
+ "Illegal redefinition of parameter o, inherited method from IX does not constrain this parameter\n" +
+ "----------\n");
+}
+// a method tries to relax the null contract, super declares @NonNull return
+public void test_parameter_specification_inheritance_005() {
+ runConformTestWithLibs(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " @NonNull Object getObject() { return new Object(); }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+ runNegativeTestWithLibs(
+ false, //dont' flush
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X extends Lib {\n" +
+ " @Override\n" +
+ " @Nullable Object getObject() { return null; }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " @Nullable Object getObject() { return null; }\n" +
+ " ^^^^^^^^^^^^^^^^\n" +
+ "The return type is incompatible with the @NonNull return from Lib.getObject()\n" +
+ "----------\n");
+}
+
+// super has no constraint for return, sub method confirms the null contract as @Nullable
+public void test_parameter_specification_inheritance_006() {
+ runConformTest(
+ new String[] {
+ "Lib.java",
+ "public class Lib {\n" +
+ " Object getObject() { return null; }\n" +
+ "}\n"
+ });
+ runConformTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X extends Lib {\n" +
+ " @Override\n" +
+ " @Nullable Object getObject() { return null; }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+}
+// a method body violates the inherited null specification, super declares @NonNull return, missing redeclaration
+public void test_parameter_specification_inheritance_007() {
+ runConformTestWithLibs(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " @NonNull Object getObject() { return new Object(); }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "public class X extends Lib {\n" +
+ " @Override\n" +
+ " Object getObject() { return null; }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 3)\n" +
+ " Object getObject() { return null; }\n" +
+ " ^^^^^^\n" +
+ "The return type is incompatible with the @NonNull return from Lib.getObject()\n" +
+ "----------\n");
+}
+//a method body violates the @NonNull return specification (repeated from super)
+public void test_parameter_specification_inheritance_007a() {
+ runConformTestWithLibs(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " @NonNull Object getObject() { return new Object(); }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X extends Lib {\n" +
+ " @Override\n" +
+ " @NonNull Object getObject() { return null; }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " @NonNull Object getObject() { return null; }\n" +
+ " ^^^^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" +
+ "----------\n");
+}
+// a client potentially violates the inherited null specification, super interface declares @NonNull parameter
+public void test_parameter_specification_inheritance_008() {
+ Map options = getCompilerOptions();
+ options.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "IX.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public interface IX {\n" +
+ " void printObject(@NonNull Object o);\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "public class X implements IX {\n" +
+ " public void printObject(Object o) { System.out.print(o.toString()); }\n" +
+ "}\n",
+ "M.java",
+ "public class M{\n" +
+ " void foo(IX x, Object o) {\n" +
+ " x.printObject(o);\n" +
+ " }\n" +
+ "}\n"
+ },
+ options,
+ "----------\n" +
+ // additional error:
+ "1. ERROR in X.java (at line 2)\n" +
+ " public void printObject(Object o) { System.out.print(o.toString()); }\n" +
+ " ^^^^^^\n" +
+ "Missing null annotation: inherited method from IX declares this parameter as @NonNull\n" +
+ "----------\n" +
+ // main error:
+ "----------\n" +
+ "1. ERROR in M.java (at line 3)\n" +
+ " x.printObject(o);\n" +
+ " ^\n" +
+ "Potential type mismatch: required \'@NonNull Object\' but nullness of the provided value is unknown\n" +
+ "----------\n");
+}
+// a static method has a more relaxed null contract than a like method in the super class, but no overriding.
+public void test_parameter_specification_inheritance_009() {
+ runConformTestWithLibs(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " @NonNull static Object getObject() { return new Object(); }\n" +
+ "}\n",
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X extends Lib {\n" +
+ " @Nullable static Object getObject() { return null; }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+}
+// class default is nonnull, method and its super both use the default
+public void test_parameter_specification_inheritance_010() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class X {\n" +
+ " protected String getString(String s) {\n" +
+ " if (Character.isLowerCase(s.charAt(0)))\n" +
+ " return getString(s);\n" +
+ " return s;\n" +
+ " }\n" +
+ "}\n",
+ "p1/Y.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class Y extends X {\n" +
+ " @Override\n" +
+ " protected String getString(String s) {\n" +
+ " return super.getString(s);\n" +
+ " }\n" +
+ "}\n",
+ },
+ customOptions,
+ "");
+}
+// class default is nonnull, method and its super both use the default, super-call passes null
+public void test_parameter_specification_inheritance_011() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class X {\n" +
+ " protected String getString(String s) {\n" +
+ " if (Character.isLowerCase(s.charAt(0)))\n" +
+ " return getString(s);\n" +
+ " return s;\n" +
+ " }\n" +
+ "}\n",
+ "p1/Y.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class Y extends X {\n" +
+ " @Override\n" +
+ " protected String getString(String s) {\n" +
+ " return super.getString(null);\n" +
+ " }\n" +
+ "}\n",
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in p1\\Y.java (at line 7)\n" +
+ " return super.getString(null);\n" +
+ " ^^^^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value is null\n" +
+ "----------\n");
+}
+// methods from two super types have different null contracts.
+// sub-class merges both using the weakest common contract
+public void test_parameter_specification_inheritance_012() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " public @Nullable String getString(String s1, @Nullable String s2, @NonNull String s3) {\n" +
+ " return s1;\n" +
+ " }\n" +
+ "}\n",
+ "p1/IY.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public interface IY {\n" +
+ " @NonNull String getString(@NonNull String s1, @NonNull String s2, @Nullable String s3);\n" +
+ "}\n",
+ "p1/Y.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Y extends X implements IY {\n" +
+ " @Override\n" +
+ " public @NonNull String getString(@Nullable String s1, @Nullable String s2, @Nullable String s3) {\n" +
+ " return \"\";\n" +
+ " }\n" +
+ "}\n",
+ },
+ customOptions,
+ "");
+}
+// methods from two super types have different null contracts.
+// sub-class overrides this method in non-conforming ways
+public void test_parameter_specification_inheritance_013() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " public @Nullable String getString(String s1, @Nullable String s2, @NonNull String s3) {\n" +
+ " return s1;\n" +
+ " }\n" +
+ "}\n",
+ "p1/IY.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public interface IY {\n" +
+ " @NonNull String getString(@NonNull String s1, @NonNull String s2, @Nullable String s3);\n" +
+ "}\n",
+ "p1/Y.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Y extends X implements IY {\n" +
+ " @Override\n" +
+ " public @Nullable String getString(String s1, @NonNull String s2, @NonNull String s3) {\n" +
+ " return \"\";\n" +
+ " }\n" +
+ "}\n",
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in p1\\Y.java (at line 5)\n" +
+ " public @Nullable String getString(String s1, @NonNull String s2, @NonNull String s3) {\n" +
+ " ^^^^^^^^^^^^^^^^\n" +
+ "The return type is incompatible with the @NonNull return from IY.getString(String, String, String)\n" +
+ "----------\n" +
+ "2. ERROR in p1\\Y.java (at line 5)\n" +
+ " public @Nullable String getString(String s1, @NonNull String s2, @NonNull String s3) {\n" +
+ " ^^^^^^\n" +
+ "Missing null annotation: inherited method from IY declares this parameter as @NonNull\n" +
+ "----------\n" +
+ "3. ERROR in p1\\Y.java (at line 5)\n" +
+ " public @Nullable String getString(String s1, @NonNull String s2, @NonNull String s3) {\n" +
+ " ^^^^^^^^^^^^^^^\n" +
+ "Illegal redefinition of parameter s2, inherited method from X declares this parameter as @Nullable\n" +
+ "----------\n" +
+ "4. ERROR in p1\\Y.java (at line 5)\n" +
+ " public @Nullable String getString(String s1, @NonNull String s2, @NonNull String s3) {\n" +
+ " ^^^^^^^^^^^^^^^\n" +
+ "Illegal redefinition of parameter s3, inherited method from IY declares this parameter as @Nullable\n" +
+ "----------\n");
+}
+// methods from two super types have different null contracts.
+// sub-class does not override, but should to bridge the incompatibility
+public void test_parameter_specification_inheritance_014() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "p1/IY.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public interface IY {\n" +
+ " public @NonNull String getString1(String s);\n" +
+ " public @NonNull String getString2(String s);\n" +
+ " public String getString3(@Nullable String s);\n" +
+ " public @NonNull String getString4(@Nullable String s);\n" +
+ " public @NonNull String getString5(@Nullable String s);\n" +
+ " public @Nullable String getString6(@NonNull String s);\n" +
+ "}\n",
+ "p1/X.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " public @Nullable String getString1(String s) {\n" + // incomp. return
+ " return s;\n" +
+ " }\n" +
+ " public String getString2(String s) {\n" + // incomp. return
+ " return s;\n" +
+ " }\n" +
+ " public String getString3(String s) {\n" + // incomp. arg
+ " return \"\";\n" +
+ " }\n" +
+ " public @NonNull String getString4(@Nullable String s) {\n" +
+ " return \"\";\n" +
+ " }\n" +
+ " public @NonNull String getString5(@NonNull String s) {\n" + // incomp. arg
+ " return s;\n" +
+ " }\n" +
+ " public @NonNull String getString6(@Nullable String s) {\n" +
+ " return \"\";\n" +
+ " }\n" +
+ "}\n",
+ "p1/Y.java",
+ "package p1;\n" +
+ "public class Y extends X implements IY {\n" +
+ "}\n",
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in p1\\Y.java (at line 2)\n" +
+ " public class Y extends X implements IY {\n" +
+ " ^\n" +
+ "The method getString1(String) from class X cannot implement the corresponding method from type IY due to incompatible nullness constraints\n" +
+ "----------\n" +
+ "2. ERROR in p1\\Y.java (at line 2)\n" +
+ " public class Y extends X implements IY {\n" +
+ " ^\n" +
+ "The method getString2(String) from class X cannot implement the corresponding method from type IY due to incompatible nullness constraints\n" +
+ "----------\n" +
+ "3. ERROR in p1\\Y.java (at line 2)\n" +
+ " public class Y extends X implements IY {\n" +
+ " ^\n" +
+ "The method getString5(String) from class X cannot implement the corresponding method from type IY due to incompatible nullness constraints\n" +
+ "----------\n" +
+ "4. ERROR in p1\\Y.java (at line 2)\n" +
+ " public class Y extends X implements IY {\n" +
+ " ^\n" +
+ "The method getString3(String) from class X cannot implement the corresponding method from type IY due to incompatible nullness constraints\n" +
+ "----------\n");
+}
+// a nullable return value is dereferenced without a check
+public void test_nullable_return_001() {
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @Nullable Object getObject() { return null; }\n" +
+ " void foo() {\n" +
+ " Object o = getObject();\n" +
+ " System.out.print(o.toString());\n" +
+ " }\n" +
+ "}\n"
+ },
+ "----------\n" +
+ "1. ERROR in X.java (at line 6)\n" +
+ " System.out.print(o.toString());\n" +
+ " ^\n" +
+ "Potential null pointer access: The variable o may be null at this location\n" +
+ "----------\n");
+}
+// a nullable return value is dereferenced without a check, method is read from .class file
+public void test_nullable_return_002() {
+ runConformTestWithLibs(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " @Nullable Object getObject() { return null; }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "public class X {\n" +
+ " void foo(Lib l) {\n" +
+ " Object o = l.getObject();\n" +
+ " System.out.print(o.toString());\n" +
+ " }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " System.out.print(o.toString());\n" +
+ " ^\n" +
+ "Potential null pointer access: The variable o may be null at this location\n" +
+ "----------\n");
+}
+// a non-null return value is checked for null, method is read from .class file
+public void test_nonnull_return_001() {
+ runConformTestWithLibs(
+ new String[] {
+ "Lib.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Lib {\n" +
+ " @NonNull Object getObject() { return new Object(); }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "");
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "X.java",
+ "public class X {\n" +
+ " void foo(Lib l) {\n" +
+ " Object o = l.getObject();\n" +
+ " if (o != null)\n" +
+ " System.out.print(o.toString());\n" +
+ " }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " if (o != null)\n" +
+ " ^\n" +
+ "Redundant null check: The variable o cannot be null at this location\n" +
+ "----------\n");
+}
+// a non-null method returns null
+public void test_nonnull_return_003() {
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @NonNull Object getObject(boolean b) {\n" +
+ " if (b)\n" +
+ " return null;\n" + // definite specification violation despite enclosing "if"
+ " return new Object();\n" +
+ " }\n" +
+ "}\n"
+ },
+ "----------\n" +
+ "1. ERROR in X.java (at line 5)\n" +
+ " return null;\n" +
+ " ^^^^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" +
+ "----------\n");
+}
+// a non-null method potentially returns null
+public void test_nonnull_return_004() {
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @NonNull Object getObject(@Nullable Object o) {\n" +
+ " return o;\n" + // 'o' is only potentially null
+ " }\n" +
+ "}\n"
+ },
+ null /*customOptions*/,
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " return o;\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" +
+ "----------\n");
+}
+// a non-null method returns its non-null argument
+public void test_nonnull_return_005() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @NonNull Object getObject(@NonNull Object o) {\n" +
+ " return o;\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "");
+}
+//a non-null method has insufficient nullness info for its return value
+public void test_nonnull_return_006() {
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @NonNull Object getObject(Object o) {\n" +
+ " return o;\n" +
+ " }\n" +
+ "}\n"
+ },
+ "----------\n" +
+ "1. WARNING in X.java (at line 4)\n" +
+ " return o;\n" +
+ " ^\n" +
+ "Potential type mismatch: required \'@NonNull Object\' but nullness of the provided value is unknown\n" +
+ "----------\n");
+}
+// a result from a nullable method is directly dereferenced
+public void test_nonnull_return_007() {
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @Nullable Object getObject() {\n" +
+ " return null;\n" +
+ " }\n" +
+ " void test() {\n" +
+ " getObject().toString();\n" +
+ " }\n" +
+ "}\n"
+ },
+ "----------\n" +
+ "1. ERROR in X.java (at line 7)\n" +
+ " getObject().toString();\n" +
+ " ^^^^^^^^^^^\n" +
+ "Potential null pointer access: The method getObject() may return null\n" +
+ "----------\n");
+}
+// a result from a nonnull method is directly checked for null: redundant
+public void test_nonnull_return_008() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @NonNull Object getObject() {\n" +
+ " return new Object();\n" +
+ " }\n" +
+ " void test() {\n" +
+ " if (getObject() == null)\n" +
+ " throw new RuntimeException();\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 7)\n" +
+ " if (getObject() == null)\n" +
+ " ^^^^^^^^^^^\n" +
+ "Redundant null check: The method getObject() cannot return null\n" +
+ "----------\n");
+}
+// a result from a nonnull method is directly checked for null (from local): redundant
+public void test_nonnull_return_009() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @NonNull Object getObject() {\n" +
+ " return new Object();\n" +
+ " }\n" +
+ " void test() {\n" +
+ " Object left = null;\n" +
+ " if (left != getObject())\n" +
+ " throw new RuntimeException();\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 8)\n" +
+ " if (left != getObject())\n" +
+ " ^^^^\n" +
+ "Redundant null check: The variable left can only be null at this location\n" +
+ "----------\n" +
+ "2. ERROR in X.java (at line 8)\n" +
+ " if (left != getObject())\n" +
+ " ^^^^^^^^^^^\n" +
+ "Redundant null check: The method getObject() cannot return null\n" +
+ "----------\n");
+}
+// a result from a nullable method is assigned and checked for null (from local): not redundant
+// see also Bug 336428 - [compiler][null] bogus warning "redundant null check" in condition of do {} while() loop
+public void test_nonnull_return_010() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @Nullable X getX() {\n" +
+ " return new X();\n" +
+ " }\n" +
+ " void test() {\n" +
+ " X left = this;\n" +
+ " do {\n" +
+ " if (left == null) \n" +
+ " throw new RuntimeException();\n" +
+ " } while ((left = left.getX()) != null);\n" + // no warning/error here!
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 9)\n" +
+ " if (left == null) \n" +
+ " ^^^^\n" +
+ "Null comparison always yields false: The variable left cannot be null at this location\n" +
+ "----------\n");
+}
+// a non-null method returns a checked-for null value, but that branch is dead code
+public void test_nonnull_return_011() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class X {\n" +
+ " Object getObject(Object dubious) {\n" +
+ " if (dubious == null)\n" + // redundant
+ " return dubious;\n" + // definitely null, but not reported inside dead code
+ " return new Object();\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 5)\n" +
+ " if (dubious == null)\n" +
+ " ^^^^^^^\n" +
+ "Null comparison always yields false: The variable dubious cannot be null at this location\n" +
+ "----------\n" +
+ "2. WARNING in X.java (at line 6)\n" +
+ " return dubious;\n" +
+ " ^^^^^^^^^^^^^^^\n" +
+ "Dead code\n" +
+ "----------\n");
+}
+// a non-null method returns a definite null from a conditional expression
+// requires the fix for Bug 354554 - [null] conditional with redundant condition yields weak error message
+// TODO(SH): ENABLE!
+public void _test_nonnull_return_012() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class X {\n" +
+ " Object getObject(Object dubious) {\n" +
+ " return dubious == null ? dubious : null;\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 5)\n" +
+ " return dubious == null ? dubious : null;\n" +
+ " ^^^^^^^\n" +
+ "Null comparison always yields false: The variable dubious cannot be null at this location\n" +
+ "----------\n" +
+ "2. ERROR in X.java (at line 5)\n" +
+ " return dubious == null ? dubious : null;\n" +
+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" +
+ "----------\n");
+}
+// don't apply any default annotations to return void
+public void test_nonnull_return_013() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class X {\n" +
+ " void getObject() {}\n" +
+ "}\n",
+ "Y.java",
+ "public class Y extends X {\n" +
+ " @Override\n" +
+ " void getObject() {}\n" + // don't complain, void takes no (default) annotation
+ "}\n"
+ },
+ customOptions,
+ "");
+}
+//suppress an error regarding null-spec violation
+public void test_suppress_001() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_SuppressOptionalErrors, CompilerOptions.ENABLED);
+ runConformTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @SuppressWarnings(\"null\")\n" +
+ " @NonNull Object getObject(@Nullable Object o) {\n" +
+ " return o;\n" + // 'o' is only potentially null
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "");
+}
+// mixed use of fully qualified name / explicit import
+public void test_annotation_import_001() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NullableAnnotationName, "org.foo.Nullable");
+ customOptions.put(CompilerOptions.OPTION_NonNullAnnotationName, "org.foo.NonNull");
+ runConformTestWithLibs(
+ new String[] {
+ CUSTOM_NULLABLE_NAME,
+ CUSTOM_NULLABLE_CONTENT,
+ CUSTOM_NONNULL_NAME,
+ CUSTOM_NONNULL_CONTENT,
+ "Lib.java",
+ "public class Lib {\n" +
+ " @org.foo.NonNull Object getObject() { return new Object(); }\n" + // FQN
+ "}\n",
+ "X.java",
+ "import org.foo.NonNull;\n" + // explicit import
+ "public class X {\n" +
+ " @NonNull Object getObject(@NonNull Lib l) {\n" +
+ " return l.getObject();\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "");
+}
+
+// use of explicit imports throughout
+public void test_annotation_import_002() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NullableAnnotationName, "org.foo.Nullable");
+ customOptions.put(CompilerOptions.OPTION_NonNullAnnotationName, "org.foo.NonNull");
+ runConformTest(
+ new String[] {
+ CUSTOM_NULLABLE_NAME,
+ CUSTOM_NULLABLE_CONTENT,
+ CUSTOM_NONNULL_NAME,
+ CUSTOM_NONNULL_CONTENT,
+ "Lib.java",
+ "import org.foo.NonNull;\n" +
+ "public class Lib {\n" +
+ " @NonNull Object getObject() { return new Object(); }\n" +
+ "}\n",
+ "X.java",
+ "import org.foo.NonNull;\n" +
+ "public class X {\n" +
+ " @NonNull Object getObject(@org.foo.Nullable String dummy, @NonNull Lib l) {\n" +
+ " Object o = l.getObject();" +
+ " return o;\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "");
+}
+// explicit import of existing annotation types
+// using a Lib without null specifications
+public void test_annotation_import_005() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NullableAnnotationName, "org.foo.MayBeNull");
+ customOptions.put(CompilerOptions.OPTION_NonNullAnnotationName, "org.foo.MustNotBeNull");
+ runNegativeTest(
+ true/*shouldFlushOutputDirectory*/,
+ new String[] {
+ "org/foo/MayBeNull.java",
+ "package org.foo;\n" +
+ "import java.lang.annotation.*;\n" +
+ "@Retention(RetentionPolicy.CLASS)\n" +
+ "public @interface MayBeNull {}\n",
+
+ "org/foo/MustNotBeNull.java",
+ "package org.foo;\n" +
+ "import java.lang.annotation.*;\n" +
+ "@Retention(RetentionPolicy.CLASS)\n" +
+ "public @interface MustNotBeNull {}\n",
+
+ "Lib.java",
+ "public class Lib {\n" +
+ " Object getObject() { return new Object(); }\n" +
+ "}\n",
+ "X.java",
+ "import org.foo.*;\n" +
+ "public class X {\n" +
+ " @MustNotBeNull Object getObject(@MustNotBeNull Lib l) {\n" +
+ " return l.getObject();\n" +
+ " }\n" +
+ "}\n",
+
+ },
+ null /*no libs*/,
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " return l.getObject();\n" +
+ " ^^^^^^^^^^^^^\n" +
+ "Potential type mismatch: required \'@MustNotBeNull Object\' but nullness of the provided value is unknown\n" +
+ "----------\n",
+ JavacTestOptions.Excuse.EclipseWarningConfiguredAsError);
+}
+// a non-null method returns a value obtained from an unannotated method, missing annotation types
+public void test_annotation_import_006() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NullableAnnotationName, "org.foo.MayBeNull");
+ customOptions.put(CompilerOptions.OPTION_NonNullAnnotationName, "org.foo.MustNotBeNull");
+ runNegativeTest(
+ true/*shouldFlushOutputDirectory*/,
+ new String[] {
+ "Lib.java",
+ "public class Lib {\n" +
+ " Object getObject() { return new Object(); }\n" +
+ "}\n",
+ "X.java",
+ "public class X {\n" +
+ " @MustNotBeNull Object getObject(@MustNotBeNull Lib l) {\n" +
+ " return l.getObject();\n" +
+ " }\n" +
+ "}\n"
+ },
+ null /* no libs */,
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 2)\n" +
+ " @MustNotBeNull Object getObject(@MustNotBeNull Lib l) {\n" +
+ " ^^^^^^^^^^^^^\n" +
+ "MustNotBeNull cannot be resolved to a type\n" +
+ "----------\n" +
+ "2. ERROR in X.java (at line 2)\n" +
+ " @MustNotBeNull Object getObject(@MustNotBeNull Lib l) {\n" +
+ " ^^^^^^^^^^^^^\n" +
+ "MustNotBeNull cannot be resolved to a type\n" +
+ "----------\n",
+ JavacTestOptions.Excuse.EclipseWarningConfiguredAsError);
+}
+// using nullness defaulting to nonnull, missing annotation types
+public void test_annotation_import_007() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportNullSpecInsufficientInfo, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NullableAnnotationName, "org.foo.MayBeNull");
+ customOptions.put(CompilerOptions.OPTION_NonNullAnnotationName, "org.foo.MustNotBeNull");
+ customOptions.put(CompilerOptions.OPTION_NonNullIsDefault, CompilerOptions.ENABLED);
+ runNegativeTest(
+ true/*shouldFlushOutputDirectory*/,
+ new String[] {
+ "Lib.java",
+ "public class Lib {\n" +
+ " Object getObject() { return new Object(); }\n" +
+ "}\n",
+ "X.java",
+ "public class X {\n" +
+ " Object getObject(Lib l) {\n" +
+ " return l.getObject();\n" +
+ " }\n" +
+ "}\n"
+ },
+ this.LIBS,
+ customOptions,
+ "----------\n" +
+ "1. ERROR in Lib.java (at line 1)\n" +
+ " public class Lib {\n" +
+ " ^\n" +
+ "Buildpath problem: the type org.foo.MustNotBeNull which is configured as a null annotation type cannot be resolved\n" +
+ "----------\n",
+ JavacTestOptions.Excuse.EclipseWarningConfiguredAsError);
+}
+
+// a null annotation is illegally used on a class:
+public void test_illegal_annotation_001() {
+ runNegativeTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNull public class X {\n" +
+ "}\n"
+ },
+ "----------\n" +
+ "1. ERROR in X.java (at line 2)\n" +
+ " @NonNull public class X {\n" +
+ " ^^^^^^^^\n" +
+ "The annotation @NonNull is disallowed for this location\n" +
+ "----------\n",
+ this.LIBS,
+ false/*shouldFlush*/);
+}
+// this test has been removed:
+// setting default to nullable, default applies to a parameter
+// public void test_default_nullness_001()
+
+// a null annotation is illegally defined by its simple name
+public void test_illegal_annotation_002() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_NonNullAnnotationName, "NichtNull");
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "public class X {\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 0)\n" +
+ " public class X {\n" +
+ " ^\n" +
+ "Cannot use the unqualified name \'NichtNull\' as an annotation name for null specification\n" +
+ "----------\n");
+}
+public void test_default_nullness_002() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NonNullIsDefault, CompilerOptions.ENABLED);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " Object getObject(@Nullable Object o) {\n" +
+ " return new Object();\n" +
+ " }\n" +
+ "}\n",
+ "Y.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Y extends X {\n" +
+ " @Override\n" +
+ " @Nullable Object getObject(Object o) {\n" + // complain illegal return redef and inherited annot is not repeated
+ " return o;\n" +
+ " }\n" +
+ "}\n",
+ },
+ customOptions,
+ // main error:
+ "----------\n" +
+ "1. ERROR in Y.java (at line 4)\n" +
+ " @Nullable Object getObject(Object o) {\n" +
+ " ^^^^^^^^^^^^^^^^\n" +
+ "The return type is incompatible with the @NonNull return from X.getObject(Object)\n" +
+ "----------\n" +
+ // additional error:
+ "2. ERROR in Y.java (at line 4)\n" +
+ " @Nullable Object getObject(Object o) {\n" +
+ " ^^^^^^\n" +
+ "Illegal redefinition of parameter o, inherited method from X declares this parameter as @Nullable\n" +
+ "----------\n");
+}
+// package default is non-null
+public void test_default_nullness_003() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class X {\n" +
+ " protected Object getObject(@Nullable Object o) {\n" +
+ " return new Object();\n" +
+ " }\n" +
+ "}\n",
+ "p2/package-info.java",
+ "@org.eclipse.jdt.annotation.NonNullByDefault\n" +
+ "package p2;\n",
+ "p2/Y.java",
+ "package p2;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Y extends p1.X {\n" +
+ " @Override\n" +
+ " protected @Nullable Object getObject(@Nullable Object o) {\n" +
+ " bar(o);\n" +
+ " return o;\n" +
+ " }\n" +
+ " void bar(Object o2) { }\n" + // parameter is nonnull per package default
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in p2\\Y.java (at line 5)\n" +
+ " protected @Nullable Object getObject(@Nullable Object o) {\n" +
+ " ^^^^^^^^^^^^^^^^\n" +
+ "The return type is incompatible with the @NonNull return from X.getObject(Object)\n" +
+ "----------\n" +
+ "2. ERROR in p2\\Y.java (at line 6)\n" +
+ " bar(o);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" +
+ "----------\n");
+}
+// package level default is consumed from package-info.class
+public void test_default_nullness_003a() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class X {\n" +
+ " protected Object getObject(@Nullable Object o) {\n" +
+ " return new Object();\n" +
+ " }\n" +
+ " protected void bar(Object o2) { }\n" + // parameter is nonnull per type default
+ "}\n",
+ "p2/package-info.java",
+ "@org.eclipse.jdt.annotation.NonNullByDefault\n" +
+ "package p2;\n",
+ },
+ customOptions,
+ "");
+ // check if default is visible from package-info.class.
+ runNegativeTestWithLibs(
+ false, // don't flush
+ new String[] {
+ "p2/Y.java",
+ "package p2;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Y extends p1.X {\n" +
+ " @Override\n" +
+ " protected @Nullable Object getObject(@Nullable Object o) {\n" + // can't override inherited default nonnull
+ " bar(o);\n" + // parameter is nonnull in super class's .class file
+ " accept(o);\n" +
+ " return o;\n" +
+ " }\n" +
+ " void accept(Object a) {}\n" + // governed by package level default
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in p2\\Y.java (at line 5)\n" +
+ " protected @Nullable Object getObject(@Nullable Object o) {\n" +
+ " ^^^^^^^^^^^^^^^^\n" +
+ "The return type is incompatible with the @NonNull return from X.getObject(Object)\n" +
+ "----------\n" +
+ "2. ERROR in p2\\Y.java (at line 6)\n" +
+ " bar(o);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" +
+ "----------\n" +
+ "3. ERROR in p2\\Y.java (at line 7)\n" +
+ " accept(o);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" +
+ "----------\n");
+}
+// don't apply type-level default to non-reference type
+public void test_default_nullness_004() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class X {\n" +
+ " protected Object getObject(boolean o) {\n" +
+ " return new Object();\n" +
+ " }\n" +
+ "}\n",
+ "p2/Y.java",
+ "package p2;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Y extends p1.X {\n" +
+ " @Override\n" +
+ " protected @NonNull Object getObject(boolean o) {\n" +
+ " return o ? this : new Object();\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "");
+}
+// package default is non-null
+// see also Bug 354536 - compiling package-info.java still depends on the order of compilation units
+public void test_default_nullness_005() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NonNullAnnotationName, "org.foo.NonNull");
+ runNegativeTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "public class X {\n" +
+ " class Inner {" +
+ " protected Object getObject(String s) {\n" +
+ " return null;\n" +
+ " }\n" +
+ " }\n" +
+ "}\n",
+ "p1/package-info.java",
+ "@org.eclipse.jdt.annotation.NonNullByDefault\n" +
+ "package p1;\n",
+ CUSTOM_NONNULL_NAME,
+ CUSTOM_NONNULL_CONTENT
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in p1\\X.java (at line 4)\n" +
+ " return null;\n" +
+ " ^^^^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" +
+ "----------\n");
+}
+// package default is non-null, package-info.java read before the annotation type
+// compile order: beginToCompile(X.Inner) triggers reading of package-info.java before the annotation type was read
+public void test_default_nullness_006() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NonNullAnnotationName, "org.foo.NonNull");
+ runNegativeTestWithLibs(
+ new String[] {
+ "p1/package-info.java",
+ "@org.eclipse.jdt.annotation.NonNullByDefault\n" +
+ "package p1;\n",
+ "p1/X.java",
+ "package p1;\n" +
+ "public class X {\n" +
+ " class Inner {" +
+ " protected Object getObject(String s) {\n" +
+ " return null;\n" +
+ " }\n" +
+ " }\n" +
+ "}\n",
+ CUSTOM_NONNULL_NAME,
+ CUSTOM_NONNULL_CONTENT
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in p1\\X.java (at line 4)\n" +
+ " return null;\n" +
+ " ^^^^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" +
+ "----------\n");
+}
+// global default nonnull, but return may be null
+public void test_default_nullness_007() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NonNullIsDefault, CompilerOptions.ENABLED);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @Nullable Object dangerous() {\n" +
+ " return null;\n" +
+ " }\n" +
+ " Object broken() {\n" +
+ " return dangerous();\n" +
+ " }\n" +
+ "}\n",
+
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 7)\n" +
+ " return dangerous();\n" +
+ " ^^^^^^^^^^^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" +
+ "----------\n");
+}
+
+// cancel type level default to comply with super specification
+public void test_default_nullness_008() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "public class X {\n" +
+ " protected Object getObject(Object o) {\n" +
+ " return new Object();\n" +
+ " }\n" +
+ "}\n",
+ "p2/Y.java",
+ "package p2;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class Y extends p1.X {\n" +
+ " @Override\n" +
+ " @NonNullByDefault(false)\n" +
+ " protected Object getObject(Object o) {\n" +
+ " if (o.toString().length() == 0)\n" + // dereference without a warning
+ " return null;\n" + // return null without a warning
+ " return o.toString();\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "");
+}
+
+// cancel outer type level default to comply with super specification
+public void test_default_nullness_009() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ runNegativeTestWithLibs(
+ new String[] {
+ "p1/X.java",
+ "package p1;\n" +
+ "public class X {\n" +
+ " protected Object getObject(Object o) {\n" +
+ " return new Object();\n" +
+ " }\n" +
+ "}\n",
+ "p2/Y.java",
+ "package p2;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class Y { \n" +
+ " @NonNullByDefault(false)\n" +
+ " static class Z extends p1.X {\n" +
+ " @Override\n" +
+ " protected Object getObject(Object o) {\n" +
+ " if (o.toString().length() == 0) {\n" +
+ " o = null;\n" + // assign null without a warning
+ " bar(o); // error: arg is declared @NonNull\n" +
+ " return null;\n" +
+ " }\n" +
+ " return o.toString();\n" +
+ " }\n" +
+ " String bar(@NonNull Object o) {\n" +
+ " return getObject(o).toString();" +
+ " }\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in p2\\Y.java (at line 11)\n" +
+ " bar(o); // error: arg is declared @NonNull\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" +
+ "----------\n");
+}
+// non-null declarations are redundant within a default scope.
+public void test_default_nullness_010() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ runConformTestWithLibs(
+ new String[] {
+ "p2/Y.java",
+ "package p2;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class Y {\n" +
+ " protected @NonNull Object getObject(@NonNull Object o) {\n" +
+ " return o;\n" +
+ " }\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. WARNING in p2\\Y.java (at line 5)\n" +
+ " protected @NonNull Object getObject(@NonNull Object o) {\n" +
+ " ^^^^^^^^^^^^^^^\n" +
+ "The nullness annotation is redundant with a default that applies to this location\n" +
+ "----------\n" +
+ "2. WARNING in p2\\Y.java (at line 5)\n" +
+ " protected @NonNull Object getObject(@NonNull Object o) {\n" +
+ " ^^^^^^^^^^^^^^^^^\n" +
+ "The nullness annotation is redundant with a default that applies to this location\n" +
+ "----------\n");
+}
+// a nonnull variable is dereferenced in a loop
+public void test_nonnull_var_in_constrol_structure_1() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NonNullIsDefault, CompilerOptions.ENABLED);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void print4(@NonNull String s) {\n" +
+ " for (int i=0; i<4; i++)\n" +
+ " print(s);\n" +
+ " }\n" +
+ " void print5(@Nullable String s) {\n" +
+ " for (int i=0; i<5; i++)\n" +
+ " print(s);\n" +
+ " }\n" +
+ " void print6(boolean b) {\n" +
+ " String s = b ? null : \"\";\n" +
+ " for (int i=0; i<5; i++)\n" +
+ " print(s);\n" +
+ " }\n" +
+ " void print(@NonNull String s) {\n" +
+ " System.out.print(s);\n" +
+ " }\n" +
+ "}\n",
+
+ },
+ customOptions,
+ "----------\n" +
+ "1. WARNING in X.java (at line 3)\n" +
+ " void print4(@NonNull String s) {\n" +
+ " ^^^^^^^^^^^^^^^^^\n" +
+ "The nullness annotation is redundant with a default that applies to this location\n" +
+ "----------\n" +
+ "2. ERROR in X.java (at line 9)\n" +
+ " print(s);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n" +
+ "3. ERROR in X.java (at line 14)\n" +
+ " print(s);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n" +
+ "4. WARNING in X.java (at line 16)\n" +
+ " void print(@NonNull String s) {\n" +
+ " ^^^^^^^^^^^^^^^^^\n" +
+ "The nullness annotation is redundant with a default that applies to this location\n" +
+ "----------\n");
+}
+// a nonnull variable is dereferenced in a finally block
+public void test_nonnull_var_in_constrol_structure_2() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NonNullIsDefault, CompilerOptions.ENABLED);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void print4(String s) {\n" +
+ " try { /*empty*/ } finally {\n" +
+ " print(s);\n" +
+ " }\n" +
+ " }\n" +
+ " void print5(@Nullable String s) {\n" +
+ " try { /*empty*/ } finally {\n" +
+ " print(s);\n" +
+ " }\n" +
+ " }\n" +
+ " void print6(boolean b) {\n" +
+ " String s = b ? null : \"\";\n" +
+ " try { /*empty*/ } finally {\n" +
+ " print(s);\n" +
+ " }\n" +
+ " }\n" +
+ " void print(String s) {\n" +
+ " System.out.print(s);\n" +
+ " }\n" +
+ "}\n",
+
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 10)\n" +
+ " print(s);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n" +
+ "2. ERROR in X.java (at line 16)\n" +
+ " print(s);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n");
+}
+// a nonnull variable is dereferenced in a finally block inside a loop
+public void test_nonnull_var_in_constrol_structure_3() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NonNullIsDefault, CompilerOptions.ENABLED);
+ customOptions.put(CompilerOptions.OPTION_ReportRedundantNullAnnotation, CompilerOptions.IGNORE);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void print4(@NonNull String s) {\n" +
+ " for (int i=0; i<4; i++)\n" +
+ " try { /*empty*/ } finally {\n" +
+ " print(s);\n" +
+ " }\n" +
+ " }\n" +
+ " void print5(@Nullable String s) {\n" +
+ " for (int i=0; i<5; i++)\n" +
+ " try { /*empty*/ } finally {\n" +
+ " print(s);\n" +
+ " }\n" +
+ " }\n" +
+ " void print6(boolean b) {\n" +
+ " String s = b ? null : \"\";\n" +
+ " for (int i=0; i<4; i++)\n" +
+ " try { /*empty*/ } finally {\n" +
+ " print(s);\n" +
+ " }\n" +
+ " }\n" +
+ " void print(@NonNull String s) {\n" +
+ " System.out.print(s);\n" +
+ " }\n" +
+ "}\n",
+
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 12)\n" +
+ " print(s);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n" +
+ "2. ERROR in X.java (at line 19)\n" +
+ " print(s);\n" +
+ " ^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n");
+}
+// a nonnull variable is dereferenced method of a nested type
+public void test_nesting_1() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, CompilerOptions.ERROR);
+ customOptions.put(CompilerOptions.OPTION_NonNullIsDefault, CompilerOptions.ENABLED);
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "@NonNullByDefault\n" +
+ "public class X {\n" +
+ " void print4(final String s1) {\n" +
+ " for (int i=0; i<3; i++)\n" +
+ " new Runnable() {\n" +
+ " public void run() {\n" +
+ " print(s1);\n" +
+ " }\n" +
+ " }.run();\n" +
+ " }\n" +
+ " void print8(final @Nullable String s2) {\n" +
+ " for (int i=0; i<3; i++)\n" +
+ " new Runnable() {\n" +
+ " public void run() {\n" +
+ " print(s2);\n" +
+ " }\n" +
+ " }.run();\n" +
+ " }\n" +
+ " void print16(boolean b) {\n" +
+ " final String s3 = b ? null : \"\";\n" +
+ " for (int i=0; i<3; i++)\n" +
+ " new Runnable() {\n" +
+ " public void run() {\n" +
+ " @NonNull String s3R = s3;\n" +
+ " }\n" +
+ " }.run();\n" +
+ " }\n" +
+ " void print(String s) {\n" +
+ " System.out.print(s);\n" +
+ " }\n" +
+ "}\n",
+
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 16)\n" +
+ " print(s2);\n" +
+ " ^^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n" +
+ "2. ERROR in X.java (at line 25)\n" +
+ " @NonNull String s3R = s3;\n" +
+ " ^^\n" +
+ "Type mismatch: required \'@NonNull String\' but the provided value can be null\n" +
+ "----------\n");
+}
+// Test a regression incurred to the OT/J based implementation
+// by the fix in Bug 360328 - [compiler][null] detect null problems in nested code (local class inside a loop)
+public void test_constructor_with_nested_class() {
+ runConformTest(
+ new String[] {
+ "X.java",
+ "public class X {\n" +
+ " final Object o1;\n" +
+ " final Object o2;\n" +
+ " public X() {\n" +
+ " this.o1 = new Object() {\n" +
+ " public String toString() { return \"O1\"; }\n" +
+ " };\n" +
+ " this.o2 = new Object();" +
+ " }\n" +
+ "}\n"
+ },
+ "");
+}
+}
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java
index 28a772e..1202528 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java
@@ -96,6 +96,7 @@
since_1_5.add(InnerEmulationTest_1_5.class);
since_1_5.add(AssignmentTest_1_5.class);
since_1_5.add(InnerClass15Test.class);
+ since_1_5.add(NullAnnotationTest.class);
// Tests to run when compliance is greater than 1.5
ArrayList since_1_6 = new ArrayList();
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15JLS4Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15JLS4Test.java
index 00678f7..060109d 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15JLS4Test.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15JLS4Test.java
@@ -7494,7 +7494,10 @@
buffer.append(typeBinding.getAnnotations().length);
typeBinding= typeBinding.getSuperclass();
}
- assertEquals("Wrong number of annotations", "000", String.valueOf(buffer));
+ // initially, this test expected "000", but after https://bugs.eclipse.org/186342
+ // annotations are resolved more eagerly, which makes the annotations on Test2 show up,
+ // which is actually the right outcome.
+ assertEquals("Wrong number of annotations", "020", String.valueOf(buffer));
}
}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15Test.java
index 2af2c81..f3af24f 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15Test.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter15Test.java
@@ -7497,7 +7497,10 @@
buffer.append(typeBinding.getAnnotations().length);
typeBinding= typeBinding.getSuperclass();
}
- assertEquals("Wrong number of annotations", "000", String.valueOf(buffer));
+ // initially, this test expected "000", but after https://bugs.eclipse.org/186342
+ // annotations are resolved more eagerly, which makes the annotations on Test2 show up,
+ // which is actually the right outcome.
+ assertEquals("Wrong number of annotations", "020", String.valueOf(buffer));
}
}
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 a89866f..9ff2c32 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
@@ -1412,6 +1412,38 @@
/** @since 3.8 */
int SwitchOnEnumNotBelow15 = TypeRelated + 890; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=360317
/**
+ * Errors/warnings from annotation based null analysis
+ */
+ /** @since 3.8 */
+ int RequiredNonNullButProvidedNull = TypeRelated + 910;
+ /** @since 3.8 */
+ int RequiredNonNullButProvidedPotentialNull = TypeRelated + 911;
+ /** @since 3.8 */
+ int RequiredNonNullButProvidedUnknown = TypeRelated + 912;
+ /** @since 3.8 */
+ int MissingNullAnnotationType = ImportRelated + 913;
+ /** @since 3.8 */
+ int NullAnnotationNameMustBeQualified = Internal + 914;
+ /** @since 3.8 */
+ int IllegalReturnNullityRedefinition = MethodRelated + 915;
+ /** @since 3.8 */
+ int IllegalRedefinitionToNonNullParameter = MethodRelated + 916;
+ /** @since 3.8 */
+ int IllegalDefinitionToNonNullParameter = MethodRelated + 917;
+ /** @since 3.8 */
+ int ParameterLackingNonNullAnnotation = MethodRelated + 918;
+ /** @since 3.8 */
+ int ParameterLackingNullableAnnotation = MethodRelated + 919;
+ /** @since 3.8 */
+ int PotentialNullMessageSendReference = Internal + 920;
+ /** @since 3.8 */
+ int RedundantNullCheckOnNonNullMessageSend = Internal + 921;
+ /** @since 3.8 */
+ int CannotImplementIncompatibleNullness = Internal + 922;
+ /** @since 3.8 */
+ int RedundantNullAnnotation = MethodRelated + 923;
+
+ /**
* External problems -- These are problems defined by other plugins
*/
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java
index 892538a..8e2ff52 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java
@@ -254,6 +254,14 @@
// this is only used for method invocation as the expression inside an expression statement
public static final int InsideExpressionStatement = Bit5;
+ // for annotation reference, signal if annotation was created from a default:
+ public static final int IsSynthetic = ASTNode.Bit7;
+ // for name reference within a memberValuePair of an annotation:
+ public static final int IsMemberValueReference = ASTNode.Bit15;
+
+ // for method declaration to avoid duplicate invocation of bindArguments()
+ public static final int HasBoundArguments = ASTNode.Bit10;
+
public ASTNode() {
super();
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 995b093..28b526d 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
@@ -10,8 +10,11 @@
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
+import java.util.Arrays;
+
import org.eclipse.jdt.core.compiler.*;
import org.eclipse.jdt.internal.compiler.*;
+import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.*;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.codegen.*;
@@ -66,13 +69,50 @@
throw new AbortMethod(this.compilationResult, problem);
}
}
+
+ /**
+ * Materialize a null annotation that has been added from the current default,
+ * in order to ensure that this annotation will be generated into the .class file, too.
+ */
+ public void addNullnessAnnotation(ReferenceBinding annotationBinding) {
+ this.annotations = addAnnotation(this, this.annotations, annotationBinding);
+ }
+
+ /**
+ * Materialize a null parameter annotation that has been added from the current default,
+ * in order to ensure that this annotation will be generated into the .class file, too.
+ */
+ public void addParameterNonNullAnnotation(int i, ReferenceBinding annotationBinding) {
+ Argument argument = this.arguments[i];
+ if (argument.type != null) // null happens for constructors of anonymous classes
+ argument.annotations = addAnnotation(argument.type, argument.annotations, annotationBinding);
+ }
+
+ private Annotation[] addAnnotation(ASTNode location, Annotation[] oldAnnotations, ReferenceBinding annotationBinding) {
+ long pos = ((long)location.sourceStart<<32) + location.sourceEnd;
+ long[] poss = new long[annotationBinding.compoundName.length];
+ Arrays.fill(poss, pos);
+ MarkerAnnotation annotation = new MarkerAnnotation(new QualifiedTypeReference(annotationBinding.compoundName, poss), location.sourceStart);
+ annotation.declarationSourceEnd = location.sourceEnd;
+ annotation.resolvedType = annotationBinding;
+ annotation.bits = IsSynthetic;
+ if (oldAnnotations == null) {
+ oldAnnotations = new Annotation[] {annotation};
+ } else {
+ int len = oldAnnotations.length;
+ System.arraycopy(oldAnnotations, 0, oldAnnotations=new Annotation[len+1], 1, len);
+ oldAnnotations[0] = annotation;
+ }
+ return oldAnnotations;
+ }
/**
* Bind and add argument's binding into the scope of the method
*/
public void bindArguments() {
- if (this.arguments != null) {
+ if (this.arguments != null && (this.bits & HasBoundArguments) == 0) {
+ this.bits |= HasBoundArguments;
// by default arguments in abstract/native methods are considered to be used (no complaint is expected)
if (this.binding == null) {
for (int i = 0, length = this.arguments.length; i < length; i++) {
@@ -85,6 +125,12 @@
for (int i = 0, length = this.arguments.length; i < length; i++) {
Argument argument = this.arguments[i];
argument.bind(this.scope, this.binding.parameters[i], used);
+ // argument.bind() 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)
+ this.binding.parameterNonNullness = new Boolean[this.arguments.length];
+ this.binding.parameterNonNullness[i] = Boolean.valueOf((argument.binding.tagBits & TagBits.AnnotationNonNull) != 0);
+ }
if (argument.annotations != null) {
if (paramAnnotations == null) {
paramAnnotations = new AnnotationBinding[length][];
@@ -143,6 +189,21 @@
}
}
+ void analyseArgumentNullity(FlowInfo info) {
+ if (this.arguments != null && this.binding.parameterNonNullness != null) {
+ for (int i = 0, count = this.arguments.length; i < count; i++) {
+ // leverage null-info from parameter annotations:
+ Boolean nonNullNess = this.binding.parameterNonNullness[i];
+ if (nonNullNess != null) {
+ if (nonNullNess.booleanValue())
+ info.markAsDefinitelyNonNull(this.arguments[i].binding);
+ else
+ info.markPotentiallyNullBit(this.arguments[i].binding);
+ }
+ }
+ }
+ }
+
public CompilationResult compilationResult() {
return this.compilationResult;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java
index 6baa903..e03c085 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java
@@ -53,6 +53,7 @@
this.arguments[i].checkNPE(currentScope, flowContext, flowInfo);
}
}
+ analyseArguments(currentScope, flowContext, flowInfo, this.binding, this.arguments);
}
// record some dependency information for exception types
ReferenceBinding[] thrownExceptions;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java
index d19c24c..f282456 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java
@@ -173,6 +173,22 @@
case TypeIds.T_JavaxAnnotationPreDestroy :
tagBits |= TagBits.AnnotationPreDestroy;
break;
+ case TypeIds.T_ConfiguredAnnotationNullable :
+ tagBits |= TagBits.AnnotationNullable;
+ break;
+ case TypeIds.T_ConfiguredAnnotationNonNull :
+ tagBits |= TagBits.AnnotationNonNull;
+ break;
+ case TypeIds.T_ConfiguredAnnotationNonNullByDefault :
+ if (valueAttribute != null
+ && valueAttribute.value instanceof FalseLiteral)
+ {
+ // parameter 'false' means: this annotation cancels any defaults
+ tagBits |= TagBits.AnnotationNullUnspecifiedByDefault;
+ break;
+ }
+ tagBits |= TagBits.AnnotationNonNullByDefault;
+ break;
}
return tagBits;
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java
index 6a8fa14..16e7fe3 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java
@@ -65,7 +65,7 @@
FlowContext.CAN_ONLY_NULL | FlowContext.IN_ASSIGNMENT, flowInfo);
}
}
- nullStatus = checkAgainstNullAnnotation(currentScope, local, nullStatus);
+ nullStatus = checkAssignmentAgainstNullAnnotation(currentScope, flowContext, local, nullStatus, this.expression);
if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) {
flowInfo.markNullStatus(local, nullStatus);
if (flowContext.initsOnFinally != null)
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java
index 8de8919..4a202cc 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java
@@ -113,9 +113,19 @@
}
}
- // tag parameters as being set
if (this.arguments != null) {
for (int i = 0, count = this.arguments.length; i < count; i++) {
+ if (this.binding.parameterNonNullness != null) {
+ // leverage null-info from parameter annotations:
+ Boolean nonNullNess = this.binding.parameterNonNullness[i];
+ if (nonNullNess != null) {
+ if (nonNullNess.booleanValue())
+ flowInfo.markAsDefinitelyNonNull(this.arguments[i].binding);
+ else
+ flowInfo.markPotentiallyNullBit(this.arguments[i].binding);
+ }
+ }
+ // tag parameters as being set
flowInfo.markAsDefinitelyAssigned(this.arguments[i].binding);
}
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java
index a9ce3fc..296364d 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * Copyright (c) 2000, 2011 IBM Corporation 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
@@ -23,6 +23,21 @@
super(left,right,operator);
}
private void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) {
+ // check sends to null-annotated methods:
+ MessageSend leftMessage = (this.left instanceof MessageSend) ? (MessageSend) this.left : null;
+ if ( leftMessage != null
+ && leftMessage.nullStatus(flowInfo) == FlowInfo.NON_NULL
+ && this.right.nullStatus(flowInfo) == FlowInfo.NULL)
+ {
+ scope.problemReporter().messageSendRedundantCheckOnNonNull(leftMessage.binding, leftMessage);
+ }
+ MessageSend rightMessage = (this.right instanceof MessageSend) ? (MessageSend) this.right : null;
+ if ( rightMessage != null
+ && rightMessage.nullStatus(flowInfo) == FlowInfo.NON_NULL
+ && this.left.nullStatus(flowInfo) == FlowInfo.NULL)
+ {
+ scope.problemReporter().messageSendRedundantCheckOnNonNull(rightMessage.binding, rightMessage);
+ }
LocalVariableBinding local = this.left.localVariableBinding();
if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java
index ba2219f..a04838a 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java
@@ -81,6 +81,7 @@
this.arguments[i].checkNPE(currentScope, flowContext, flowInfo);
}
}
+ analyseArguments(currentScope, flowContext, flowInfo, this.binding, this.arguments);
}
ReferenceBinding[] thrownExceptions;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java
index b11e476..7a33ee6 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java
@@ -85,7 +85,7 @@
this.bits &= ~FirstAssignmentToLocal; // int i = (i = 0);
}
flowInfo.markAsDefinitelyAssigned(this.binding);
- nullStatus = checkAgainstNullAnnotation(currentScope, this.binding, nullStatus);
+ nullStatus = checkAssignmentAgainstNullAnnotation(currentScope, flowContext, this.binding, nullStatus, this.initialization);
if ((this.binding.type.tagBits & TagBits.IsBaseType) == 0) {
flowInfo.markNullStatus(this.binding, nullStatus);
// no need to inform enclosing try block since its locals won't get
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MemberValuePair.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MemberValuePair.java
index a083742..40878dc 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MemberValuePair.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MemberValuePair.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * Copyright (c) 2000, 2011 IBM Corporation 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
@@ -41,6 +41,9 @@
if (value instanceof ArrayInitializer) {
value.bits |= IsAnnotationDefaultValue;
}
+ if (value instanceof NameReference) {
+ value.bits |= IsMemberValueReference;
+ }
}
/* (non-Javadoc)
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 795a345..456370a 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
@@ -99,6 +99,7 @@
flowInfo = FakedTrackingVariable.markPassedToOutside(currentScope, this.arguments[i], flowInfo);
flowInfo = this.arguments[i].analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
}
+ analyseArguments(currentScope, flowContext, flowInfo, this.binding, this.arguments);
}
ReferenceBinding[] thrownExceptions;
if ((thrownExceptions = this.binding.thrownExceptions) != Binding.NO_EXCEPTIONS) {
@@ -114,6 +115,11 @@
}
manageSyntheticAccessIfNecessary(currentScope, flowInfo);
return flowInfo;
+}
+public void checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) {
+ super.checkNPE(scope, flowContext, flowInfo);
+ if (nullStatus(flowInfo) == FlowInfo.POTENTIALLY_NULL)
+ scope.problemReporter().messageSendPotentialNullReference(this.binding, this);
}
/**
* @see org.eclipse.jdt.internal.compiler.ast.Expression#computeConversion(org.eclipse.jdt.internal.compiler.lookup.Scope, org.eclipse.jdt.internal.compiler.lookup.TypeBinding, org.eclipse.jdt.internal.compiler.lookup.TypeBinding)
@@ -268,9 +274,16 @@
}
}
public int nullStatus(FlowInfo flowInfo) {
+ if (this.binding.isValidBinding()) {
+ // try to retrieve null status of this message send from an annotation of the called method:
+ long tagBits = this.binding.tagBits;
+ if ((tagBits & TagBits.AnnotationNonNull) != 0)
+ return FlowInfo.NON_NULL;
+ if ((tagBits & TagBits.AnnotationNullable) != 0)
+ return FlowInfo.POTENTIALLY_NULL;
+ }
return FlowInfo.UNKNOWN;
}
-
/**
* @see org.eclipse.jdt.internal.compiler.ast.Expression#postConversionType(Scope)
*/
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java
index 5a5c24c..af17100 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java
@@ -79,9 +79,19 @@
this.scope,
FlowInfo.DEAD_END);
- // tag parameters as being set
if (this.arguments != null) {
for (int i = 0, count = this.arguments.length; i < count; i++) {
+ if (this.binding.parameterNonNullness != null) {
+ // leverage null-info from parameter annotations:
+ Boolean nonNullNess = this.binding.parameterNonNullness[i];
+ if (nonNullNess != null) {
+ if (nonNullNess.booleanValue())
+ flowInfo.markAsDefinitelyNonNull(this.arguments[i].binding);
+ else
+ flowInfo.markPotentiallyNullBit(this.arguments[i].binding);
+ }
+ }
+ // tag parameters as being set:
flowInfo.markAsDefinitelyAssigned(this.arguments[i].binding);
// if this method uses a type parameter declared by the declaring class,
// it can't be static. https://bugs.eclipse.org/bugs/show_bug.cgi?id=318682
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java
index 93bf5b5..0befde9 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java
@@ -86,6 +86,7 @@
this.arguments[i].checkNPE(currentScope, flowContext, flowInfo);
}
}
+ analyseArguments(currentScope, flowContext, flowInfo, this.binding, this.arguments);
}
// analyse the anonymous nested type
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java
index 1b4a530..613528a 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java
@@ -963,7 +963,11 @@
&& methodScope.lastVisibleFieldID >= 0
&& fieldBinding.id >= methodScope.lastVisibleFieldID
&& (!fieldBinding.isStatic() || methodScope.isStatic)) {
- scope.problemReporter().forwardReference(this, this.indexOfFirstFieldBinding-1, fieldBinding);
+ if ((this.bits & IsMemberValueReference) != 0 && fieldBinding.id == methodScope.lastVisibleFieldID) {
+ // false alarm, location is NOT a field initializer but the value in a memberValuePair
+ } else {
+ scope.problemReporter().forwardReference(this, this.indexOfFirstFieldBinding-1, fieldBinding);
+ }
}
if (isFieldUseDeprecated(fieldBinding, scope, this.indexOfFirstFieldBinding == this.tokens.length ? this.bits : 0)) {
scope.problemReporter().deprecatedField(fieldBinding, this);
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java
index 520cf38..0966573 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java
@@ -44,6 +44,8 @@
if ((this.expression.implicitConversion & TypeIds.UNBOXING) != 0) {
this.expression.checkNPE(currentScope, flowContext, flowInfo);
}
+ if (flowInfo.reachMode() == FlowInfo.REACHABLE)
+ checkAgainstNullAnnotation(currentScope, this.expression.nullStatus(flowInfo));
FakedTrackingVariable trackingVariable = FakedTrackingVariable.getCloseTrackingVariable(this.expression);
if (trackingVariable != null) {
if (methodScope != trackingVariable.methodScope)
@@ -119,6 +121,23 @@
currentScope.checkUnclosedCloseables(flowInfo, this, currentScope);
return FlowInfo.DEAD_END;
}
+void checkAgainstNullAnnotation(BlockScope scope, int nullStatus) {
+ if (nullStatus != FlowInfo.NON_NULL) {
+ // if we can't prove non-null check against declared null-ness of the enclosing method:
+ long tagBits;
+ MethodBinding methodBinding;
+ try {
+ methodBinding = scope.methodScope().referenceMethod().binding;
+ tagBits = methodBinding.tagBits;
+ } catch (NullPointerException npe) {
+ return;
+ }
+ if ((tagBits & TagBits.AnnotationNonNull) != 0) {
+ char[][] annotationName = scope.environment().getNonNullAnnotationName();
+ scope.problemReporter().nullityMismatch(this.expression, methodBinding.returnType, nullStatus, annotationName);
+ }
+ }
+}
/**
* Retrun statement code generation
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java
index 99d0fcb..975ca3b 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java
@@ -57,8 +57,35 @@
public static final int COMPLAINED_FAKE_REACHABLE = 1;
public static final int COMPLAINED_UNREACHABLE = 2;
-/** Empty hook for checking null status against declaration using null annotations, once this will be supported. */
-protected int checkAgainstNullAnnotation(BlockScope currentScope, LocalVariableBinding local, int nullStatus) {
+
+/** Analysing arguments of MessageSend, ExplicitConstructorCall, AllocationExpression. */
+protected void analyseArguments(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, MethodBinding methodBinding, Expression[] arguments)
+{
+ // compare actual null-status against parameter annotations of the called method:
+ if (arguments != null && methodBinding.parameterNonNullness != null) {
+ for (int i = 0; i < arguments.length; i++) {
+ if (methodBinding.parameterNonNullness[i] == Boolean.TRUE) {
+ TypeBinding expectedType = methodBinding.parameters[i];
+ Expression argument = arguments[i];
+ int nullStatus = argument.nullStatus(flowInfo); // slight loss of precision: should also use the null info from the receiver.
+ if (nullStatus != FlowInfo.NON_NULL) // if required non-null is not provided
+ flowContext.recordNullityMismatch(currentScope, argument, nullStatus, expectedType);
+ }
+ }
+ }
+}
+
+/** Check null-ness of 'local' against a possible null annotation */
+protected int checkAssignmentAgainstNullAnnotation(BlockScope currentScope, FlowContext flowContext,
+ LocalVariableBinding local, int nullStatus, Expression expression)
+{
+ if ( local != null
+ && (local.tagBits & TagBits.AnnotationNonNull) != 0
+ && nullStatus != FlowInfo.NON_NULL)
+ {
+ flowContext.recordNullityMismatch(currentScope, expression, nullStatus, local.type);
+ nullStatus=FlowInfo.NON_NULL;
+ }
return nullStatus;
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java
index 7f7638d..fbad42e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java
@@ -17,6 +17,7 @@
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
@@ -83,8 +84,13 @@
// check inconsistent null checks
if ((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0) { // within an enclosing loop, be conservative
for (int i = 0; i < this.nullCount; i++) {
- this.parent.recordUsingNullReference(scope, this.nullLocals[i],
- this.nullReferences[i], this.nullCheckTypes[i], flowInfo);
+ if (this.nullCheckTypes[i] == ASSIGN_TO_NONNULL)
+ this.parent.recordNullityMismatch(scope, this.nullReferences[i],
+ flowInfo.nullStatus(this.nullLocals[i]), this.expectedTypes[i]);
+ else
+ this.parent.recordUsingNullReference(scope, this.nullLocals[i],
+ this.nullReferences[i], this.nullCheckTypes[i], flowInfo);
+
}
}
else { // no enclosing loop, be as precise as possible right now
@@ -165,6 +171,13 @@
}
if (flowInfo.isPotentiallyNull(local)) {
scope.problemReporter().localVariablePotentialNullReference(local, expression);
+ }
+ break;
+ case ASSIGN_TO_NONNULL:
+ int nullStatus = flowInfo.nullStatus(local);
+ if (nullStatus != FlowInfo.NON_NULL) {
+ char[][] annotationName = scope.environment().getNonNullAnnotationName();
+ scope.problemReporter().nullityMismatch(expression, this.expectedTypes[i], nullStatus, annotationName);
}
break;
default:
@@ -442,4 +455,14 @@
this.nullReferences[this.nullCount] = expression;
this.nullCheckTypes[this.nullCount++] = status;
}
+protected boolean internalRecordNullityMismatch(Expression expression, int nullStatus, TypeBinding expectedType, int checkType) {
+ // cf. decision structure inside FinallyFlowContext.recordUsingNullReference(..)
+ if (nullStatus == FlowInfo.UNKNOWN ||
+ ((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0 && nullStatus != FlowInfo.NULL)) {
+ recordExpectedType(expectedType, this.nullCount);
+ recordNullReference(expression.localVariableBinding(), expression, checkType);
+ return true;
+ }
+ return false;
+}
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java
index ccb5fb6..2e7737b 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java
@@ -52,6 +52,10 @@
// any null related operation happening within the try block
public int tagBits;
+
+ // array to store the expected type from the potential error location (for display in error messages):
+ public TypeBinding[] expectedTypes = null;
+
public static final int DEFER_NULL_DIAGNOSTIC = 0x1;
public static final int PREEMPT_NULL_DIAGNOSTIC = 0x2;
/**
@@ -66,6 +70,8 @@
public static final int CAN_ONLY_NON_NULL = 0x0002;
//check against non null, with definite values -- comparisons
public static final int MAY_NULL = 0x0003;
+//check binding a value to a @NonNull variable
+public final static int ASSIGN_TO_NONNULL = 0x0080;
// check against null, with potential values -- NPE guard
public static final int CHECK_MASK = 0x00FF;
public static final int IN_COMPARISON_NULL = 0x0100;
@@ -548,6 +554,21 @@
// default implementation: do nothing
}
+protected void recordExpectedType(TypeBinding expectedType, int nullCount) {
+ if (nullCount == 0) {
+ this.expectedTypes = new TypeBinding[5];
+ } else if (this.expectedTypes == null) {
+ int size = 5;
+ while (size <= nullCount) size *= 2;
+ this.expectedTypes = new TypeBinding[size];
+ }
+ else if (nullCount == this.expectedTypes.length) {
+ System.arraycopy(this.expectedTypes, 0,
+ this.expectedTypes = new TypeBinding[nullCount * 2], 0, nullCount);
+ }
+ this.expectedTypes[nullCount] = expectedType;
+}
+
protected boolean recordFinalAssignment(VariableBinding variable, Reference finalReference) {
return true; // keep going
}
@@ -746,4 +767,31 @@
buffer.append(individualToString()).append('\n');
return buffer.toString();
}
+
+/**
+ * Record that a nullity mismatch was detected against an annotated type reference.
+ * @param currentScope scope for error reporting
+ * @param expression the expression violating the specification
+ * @param nullStatus the null status of expression at the current location
+ * @param expectedType the declared type of the spec'ed variable, for error reporting.
+ */
+public void recordNullityMismatch(BlockScope currentScope, Expression expression, int nullStatus, TypeBinding expectedType) {
+ if (expression.localVariableBinding() != null) { // flowContext cannot yet handle non-localvar expressions (e.g., fields)
+ // find the inner-most flowContext that might need deferred handling:
+ FlowContext currentContext = this;
+ while (currentContext != null) {
+ // some flow contexts implement deferred checking, should we participate in that?
+ if (currentContext.internalRecordNullityMismatch(expression, nullStatus, expectedType, ASSIGN_TO_NONNULL))
+ return;
+ currentContext = currentContext.parent;
+ }
+ }
+ // no reason to defer, so report now:
+ char[][] annotationName = currentScope.environment().getNonNullAnnotationName();
+ currentScope.problemReporter().nullityMismatch(expression, expectedType, nullStatus, annotationName);
+}
+protected boolean internalRecordNullityMismatch(Expression expression, int nullStatus, TypeBinding expectedType, int checkType) {
+ // nop, to be overridden in subclasses
+ return false; // not recorded
+}
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java
index 919bb4c..3e9d4cd 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java
@@ -12,6 +12,7 @@
package org.eclipse.jdt.internal.compiler.flow;
import java.util.ArrayList;
+
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.Reference;
@@ -21,6 +22,7 @@
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
@@ -246,6 +248,9 @@
continue;
}
break;
+ case ASSIGN_TO_NONNULL:
+ this.parent.recordNullityMismatch(scope, expression, flowInfo.nullStatus(local), this.expectedTypes[i]);
+ break;
default:
// never happens
}
@@ -337,6 +342,13 @@
this.nullReferences[i] = null;
scope.problemReporter().localVariablePotentialNullReference(local, expression);
continue;
+ }
+ break;
+ case ASSIGN_TO_NONNULL:
+ int nullStatus = flowInfo.nullStatus(local);
+ if (nullStatus != FlowInfo.NON_NULL) {
+ char[][] annotationName = scope.environment().getNonNullAnnotationName();
+ scope.problemReporter().nullityMismatch(expression, this.expectedTypes[i], nullStatus, annotationName);
}
break;
default:
@@ -667,4 +679,10 @@
public boolean hasEscapingExceptions() {
return this.escapingExceptionCatchSites != null;
}
+
+ protected boolean internalRecordNullityMismatch(Expression expression, int nullStatus, TypeBinding expectedType, int checkType) {
+ recordExpectedType(expectedType, this.nullCount);
+ recordNullReference(expression.localVariableBinding(), expression, checkType);
+ return true;
+ }
}
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 b269d89..c8c50df 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
@@ -26,6 +26,7 @@
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
+import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.eclipse.jdt.internal.compiler.util.Util;
@@ -142,6 +143,19 @@
public static final String OPTION_ReportUnclosedCloseable = "org.eclipse.jdt.core.compiler.problem.unclosedCloseable"; //$NON-NLS-1$
public static final String OPTION_ReportPotentiallyUnclosedCloseable = "org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable"; //$NON-NLS-1$
public static final String OPTION_ReportExplicitlyClosedAutoCloseable = "org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable"; //$NON-NLS-1$
+ public static final String OPTION_ReportNullSpecViolation = "org.eclipse.jdt.core.compiler.problem.nullSpecViolation"; //$NON-NLS-1$
+ public static final String OPTION_ReportPotentialNullSpecViolation = "org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation"; //$NON-NLS-1$
+ public static final String OPTION_ReportNullSpecInsufficientInfo = "org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo"; //$NON-NLS-1$
+ public static final String OPTION_ReportRedundantNullAnnotation = "org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation"; //$NON-NLS-1$
+ public static final String OPTION_AnnotationBasedNullAnalysis = "org.eclipse.jdt.core.compiler.annotation.nullanalysis"; //$NON-NLS-1$
+ public static final String OPTION_NullableAnnotationName = "org.eclipse.jdt.core.compiler.annotation.nullable"; //$NON-NLS-1$
+ public static final String OPTION_NonNullAnnotationName = "org.eclipse.jdt.core.compiler.annotation.nonnull"; //$NON-NLS-1$
+ public static final String OPTION_NonNullByDefaultAnnotationName = "org.eclipse.jdt.core.compiler.annotation.nonnullbydefault"; //$NON-NLS-1$
+ // defaults for the above:
+ static final char[][] DEFAULT_NULLABLE_ANNOTATION_NAME = CharOperation.splitOn('.', "org.eclipse.jdt.annotation.Nullable".toCharArray()); //$NON-NLS-1$
+ 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_NonNullIsDefault = "org.eclipse.jdt.core.compiler.annotation.nonnullisdefault"; //$NON-NLS-1$
/**
* Possible values for configurable options
*/
@@ -245,10 +259,13 @@
public static final int MethodCanBeStatic = IrritantSet.GROUP2 | ASTNode.Bit5;
public static final int MethodCanBePotentiallyStatic = IrritantSet.GROUP2 | ASTNode.Bit6;
public static final int RedundantSpecificationOfTypeArguments = IrritantSet.GROUP2 | ASTNode.Bit7;
- // bits 8-10 reserved for https://bugs.eclipse.org/bugs/show_bug.cgi?id=186342
- public static final int UnclosedCloseable = IrritantSet.GROUP2 | ASTNode.Bit11;
- public static final int PotentiallyUnclosedCloseable = IrritantSet.GROUP2 | ASTNode.Bit12;
- public static final int ExplicitlyClosedAutoCloseable = IrritantSet.GROUP2 | ASTNode.Bit13;
+ public static final int UnclosedCloseable = IrritantSet.GROUP2 | ASTNode.Bit8;
+ public static final int PotentiallyUnclosedCloseable = IrritantSet.GROUP2 | ASTNode.Bit9;
+ public static final int ExplicitlyClosedAutoCloseable = IrritantSet.GROUP2 | ASTNode.Bit10;
+ public static final int NullSpecViolation = IrritantSet.GROUP2 | ASTNode.Bit11;
+ public static final int PotentialNullSpecViolation = IrritantSet.GROUP2 | ASTNode.Bit12;
+ public static final int NullSpecInsufficientInfo = IrritantSet.GROUP2 | ASTNode.Bit13;
+ public static final int RedundantNullAnnotation = IrritantSet.GROUP2 | ASTNode.Bit14;
// Severity level for handlers
/**
@@ -370,6 +387,18 @@
public boolean includeNullInfoFromAsserts;
/** Controls whether forced generic type problems get reported */
public boolean reportUnavoidableGenericTypeProblems;
+
+ // === Support for Null Annotations: ===
+ /** Master switch for null analysis based on annotations: */
+ public boolean isAnnotationBasedNullAnalysisEnabled;
+ /** Fully qualified name of annotation to use as marker for nullable types. */
+ public char[][] nullableAnnotationName;
+ /** Fully qualified name of annotation to use as marker for nonnull types. */
+ public char[][] nonNullAnnotationName;
+ /** Fully qualified name of annotation to use as marker for default nonnull. */
+ public char[][] nonNullByDefaultAnnotationName;
+ /** TagBits-encoded default for non-annotated types. */
+ public long defaultNonNullness; // 0 or TagBits#AnnotationNonNull
// keep in sync with warningTokenToIrritant and warningTokenFromIrritant
public final static String[] warningTokens = {
@@ -567,6 +596,14 @@
return OPTION_ReportPotentiallyUnclosedCloseable;
case ExplicitlyClosedAutoCloseable :
return OPTION_ReportExplicitlyClosedAutoCloseable;
+ case NullSpecViolation :
+ return OPTION_ReportNullSpecViolation;
+ case PotentialNullSpecViolation :
+ return OPTION_ReportPotentialNullSpecViolation;
+ case NullSpecInsufficientInfo :
+ return OPTION_ReportNullSpecInsufficientInfo;
+ case RedundantNullAnnotation :
+ return OPTION_ReportRedundantNullAnnotation;
}
return null;
}
@@ -733,6 +770,14 @@
OPTION_ReportUnclosedCloseable,
OPTION_ReportPotentiallyUnclosedCloseable,
OPTION_ReportExplicitlyClosedAutoCloseable,
+ OPTION_AnnotationBasedNullAnalysis,
+ OPTION_NonNullAnnotationName,
+ OPTION_NullableAnnotationName,
+ OPTION_NonNullByDefaultAnnotationName,
+ OPTION_NonNullIsDefault,
+ OPTION_ReportNullSpecViolation,
+ OPTION_ReportPotentialNullSpecViolation,
+ OPTION_ReportRedundantNullAnnotation
};
return result;
}
@@ -795,6 +840,10 @@
case NullReference :
case PotentialNullReference :
case RedundantNullCheck :
+ case NullSpecViolation :
+ case PotentialNullSpecViolation :
+ case NullSpecInsufficientInfo :
+ case RedundantNullAnnotation :
return "null"; //$NON-NLS-1$
case FallthroughCase :
return "fallthrough"; //$NON-NLS-1$
@@ -1008,6 +1057,18 @@
optionsMap.put(OPTION_ReportUnclosedCloseable, getSeverityString(UnclosedCloseable));
optionsMap.put(OPTION_ReportPotentiallyUnclosedCloseable, getSeverityString(PotentiallyUnclosedCloseable));
optionsMap.put(OPTION_ReportExplicitlyClosedAutoCloseable, getSeverityString(ExplicitlyClosedAutoCloseable));
+ optionsMap.put(OPTION_AnnotationBasedNullAnalysis, this.isAnnotationBasedNullAnalysisEnabled ? ENABLED : DISABLED);
+ optionsMap.put(OPTION_ReportNullSpecViolation, getSeverityString(NullSpecViolation));
+ optionsMap.put(OPTION_ReportPotentialNullSpecViolation, getSeverityString(PotentialNullSpecViolation));
+ optionsMap.put(OPTION_ReportNullSpecInsufficientInfo, getSeverityString(NullSpecInsufficientInfo));
+ optionsMap.put(OPTION_ReportRedundantNullAnnotation, getSeverityString(RedundantNullAnnotation));
+ optionsMap.put(OPTION_NullableAnnotationName, String.valueOf(CharOperation.concatWith(this.nullableAnnotationName, '.')));
+ optionsMap.put(OPTION_NonNullAnnotationName, String.valueOf(CharOperation.concatWith(this.nonNullAnnotationName, '.')));
+ optionsMap.put(OPTION_NonNullByDefaultAnnotationName, String.valueOf(CharOperation.concatWith(this.nonNullByDefaultAnnotationName, '.')));
+ if (this.defaultNonNullness == TagBits.AnnotationNonNull)
+ optionsMap.put(OPTION_NonNullIsDefault, CompilerOptions.ENABLED);
+ else
+ optionsMap.put(OPTION_NonNullIsDefault, CompilerOptions.DISABLED);
return optionsMap;
}
@@ -1158,6 +1219,11 @@
// allow null info from asserts to be considered downstream by default
this.includeNullInfoFromAsserts = false;
+
+ this.isAnnotationBasedNullAnalysisEnabled = false;
+ this.nullableAnnotationName = DEFAULT_NULLABLE_ANNOTATION_NAME;
+ this.nonNullAnnotationName = DEFAULT_NONNULL_ANNOTATION_NAME;
+ this.nonNullByDefaultAnnotationName = DEFAULT_NONNULLBYDEFAULT_ANNOTATION_NAME;
}
public void set(Map optionsMap) {
@@ -1441,6 +1507,30 @@
if ((optionValue = optionsMap.get(OPTION_ReportUnclosedCloseable)) != null) updateSeverity(UnclosedCloseable, optionValue);
if ((optionValue = optionsMap.get(OPTION_ReportPotentiallyUnclosedCloseable)) != null) updateSeverity(PotentiallyUnclosedCloseable, optionValue);
if ((optionValue = optionsMap.get(OPTION_ReportExplicitlyClosedAutoCloseable)) != null) updateSeverity(ExplicitlyClosedAutoCloseable, optionValue);
+ if ((optionValue = optionsMap.get(OPTION_AnnotationBasedNullAnalysis)) != null) {
+ this.isAnnotationBasedNullAnalysisEnabled = ENABLED.equals(optionValue);
+ }
+ if (this.isAnnotationBasedNullAnalysisEnabled) {
+ if ((optionValue = optionsMap.get(OPTION_ReportNullSpecViolation)) != null) updateSeverity(NullSpecViolation, optionValue);
+ if ((optionValue = optionsMap.get(OPTION_ReportPotentialNullSpecViolation)) != null) updateSeverity(PotentialNullSpecViolation, optionValue);
+ if ((optionValue = optionsMap.get(OPTION_ReportNullSpecInsufficientInfo)) != null) updateSeverity(NullSpecInsufficientInfo, optionValue);
+ if ((optionValue = optionsMap.get(OPTION_ReportRedundantNullAnnotation)) != null) updateSeverity(RedundantNullAnnotation, optionValue);
+ if ((optionValue = optionsMap.get(OPTION_NullableAnnotationName)) != null) {
+ this.nullableAnnotationName = CharOperation.splitAndTrimOn('.', ((String)optionValue).toCharArray());
+ }
+ if ((optionValue = optionsMap.get(OPTION_NonNullAnnotationName)) != null) {
+ this.nonNullAnnotationName = CharOperation.splitAndTrimOn('.', ((String)optionValue).toCharArray());
+ }
+ if ((optionValue = optionsMap.get(OPTION_NonNullByDefaultAnnotationName)) != null) {
+ this.nonNullByDefaultAnnotationName = CharOperation.splitAndTrimOn('.', ((String)optionValue).toCharArray());
+ }
+ if ((optionValue = optionsMap.get(OPTION_NonNullIsDefault)) != null) {
+ if (CompilerOptions.ENABLED.equals(optionValue))
+ this.defaultNonNullness = TagBits.AnnotationNonNull;
+ else if (CompilerOptions.DISABLED.equals(optionValue))
+ this.defaultNonNullness = 0;
+ }
+ }
// Javadoc options
if ((optionValue = optionsMap.get(OPTION_DocCommentSupport)) != null) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java
index 38dd14f..3b3aca8 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java
@@ -102,8 +102,14 @@
.set(
CompilerOptions.DeadCode
|CompilerOptions.Tasks
- |CompilerOptions.UnclosedCloseable);
-
+ |CompilerOptions.UnclosedCloseable
+ |CompilerOptions.NullSpecInsufficientInfo
+ |CompilerOptions.RedundantNullAnnotation);
+ // default errors IF AnnotationBasedNullAnalysis is enabled:
+ COMPILER_DEFAULT_ERRORS.set(
+ CompilerOptions.NullSpecViolation
+ |CompilerOptions.PotentialNullSpecViolation);
+
ALL.setAll();
HIDING
.set(CompilerOptions.FieldHiding)
@@ -111,7 +117,12 @@
.set(CompilerOptions.TypeHiding);
NULL
.set(CompilerOptions.PotentialNullReference)
- .set(CompilerOptions.RedundantNullCheck);
+ .set(CompilerOptions.RedundantNullCheck)
+ .set(CompilerOptions.NullSpecViolation)
+ .set(CompilerOptions.PotentialNullSpecViolation)
+ .set(CompilerOptions.NullSpecInsufficientInfo)
+ .set(CompilerOptions.RedundantNullAnnotation);
+
RESTRICTION.set(CompilerOptions.DiscouragedReference);
STATIC_ACCESS.set(CompilerOptions.NonStaticAccessToStatic);
UNUSED
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 b8581e3..e9abe25 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
@@ -16,6 +16,7 @@
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.*;
+import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
@@ -383,6 +384,8 @@
}
if (this.environment.globalOptions.storeAnnotations)
setAnnotations(createAnnotations(binaryType.getAnnotations(), this.environment, missingTypeNames));
+
+ scanTypeForNullAnnotation(binaryType);
} finally {
// protect against incorrect use of the needFieldsAndMethods flag, see 48459
if (this.fields == null)
@@ -589,6 +592,9 @@
// fixup the declaring element of the type variable
for (int i = 0, length = typeVars.length; i < length; i++)
typeVars[i].declaringElement = result;
+
+ scanMethodForNullAnnotation(method, result);
+
return result;
}
@@ -1142,6 +1148,96 @@
}
return this.storedAnnotations;
}
+void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding methodBinding) {
+ char[][] nullableAnnotationName = this.environment.getNullableAnnotationName();
+ char[][] nonNullAnnotationName = this.environment.getNonNullAnnotationName();
+ if (nullableAnnotationName == null || nonNullAnnotationName == null)
+ return; // not configured to use null annotations
+
+ // return:
+ IBinaryAnnotation[] annotations = method.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.length; i++) {
+ char[] annotationTypeName = annotations[i].getTypeName();
+ if (annotationTypeName[0] != 'L')
+ continue;
+ char[][] typeName = CharOperation.splitOn('/', annotationTypeName, 1, annotationTypeName.length-1); // cut of leading 'L' and trailing ';'
+ if (CharOperation.equals(typeName, nonNullAnnotationName)) {
+ methodBinding.tagBits |= TagBits.AnnotationNonNull;
+ break;
+ }
+ if (CharOperation.equals(typeName, nullableAnnotationName)) {
+ methodBinding.tagBits |= TagBits.AnnotationNullable;
+ break;
+ }
+ }
+ }
+
+ // parameters:
+ TypeBinding[] parameters = methodBinding.parameters;
+ for (int j = 0; j < parameters.length; j++) {
+ IBinaryAnnotation[] paramAnnotations = method.getParameterAnnotations(j);
+ if (paramAnnotations != null) {
+ for (int i = 0; i < paramAnnotations.length; i++) {
+ char[] annotationTypeName = paramAnnotations[i].getTypeName();
+ if (annotationTypeName[0] != 'L')
+ continue;
+ char[][] typeName = CharOperation.splitOn('/', annotationTypeName, 1, annotationTypeName.length-1); // cut of leading 'L' and trailing ';'
+ if (CharOperation.equals(typeName, nonNullAnnotationName)) {
+ if (methodBinding.parameterNonNullness == null)
+ methodBinding.parameterNonNullness = new Boolean[parameters.length];
+ methodBinding.parameterNonNullness[j] = Boolean.TRUE;
+ break;
+ } else if (CharOperation.equals(typeName, nullableAnnotationName)) {
+ if (methodBinding.parameterNonNullness == null)
+ methodBinding.parameterNonNullness = new Boolean[parameters.length];
+ methodBinding.parameterNonNullness[j] = Boolean.FALSE;
+ break;
+ }
+ }
+ }
+ }
+}
+void scanTypeForNullAnnotation(IBinaryType binaryType) {
+ char[][] nonNullByDefaultAnnotationName = this.environment.getNonNullByDefaultAnnotationName();
+ if (nonNullByDefaultAnnotationName == null)
+ return; // not configured to use null annotations
+
+ IBinaryAnnotation[] annotations = binaryType.getAnnotations();
+ if (annotations != null) {
+ long annotationBit = 0L;
+ TypeBinding defaultNullness = null;
+ for (int i = 0; i < annotations.length; i++) {
+ char[] annotationTypeName = annotations[i].getTypeName();
+ if (annotationTypeName[0] != 'L')
+ continue;
+ char[][] typeName = CharOperation.splitOn('/', annotationTypeName, 1, annotationTypeName.length-1); // cut of leading 'L' and trailing ';'
+ if (CharOperation.equals(typeName, nonNullByDefaultAnnotationName)) {
+ IBinaryElementValuePair[] elementValuePairs = annotations[i].getElementValuePairs();
+ if (elementValuePairs != null && elementValuePairs.length == 1) {
+ Object value = elementValuePairs[0].getValue();
+ if (value instanceof BooleanConstant
+ && !((BooleanConstant)value).booleanValue())
+ {
+ // parameter is 'false': this means we cancel defaults from outer scopes:
+ annotationBit = TagBits.AnnotationNullUnspecifiedByDefault;
+ defaultNullness = ReferenceBinding.NULL_UNSPECIFIED;
+ break;
+ }
+ }
+ annotationBit = TagBits.AnnotationNonNullByDefault;
+ defaultNullness = this.environment.getNullAnnotationBinding(TagBits.AnnotationNonNull, false/*resolve*/);
+ break;
+ }
+ }
+ if (annotationBit != 0L) {
+ this.tagBits |= annotationBit;
+ if (CharOperation.equals(this.sourceName(), TypeConstants.PACKAGE_INFO_NAME))
+ this.getPackage().nullnessDefaultAnnotation = defaultNullness;
+ }
+ }
+}
+
/* Answer the receiver's superclass... null if the receiver is Object or an interface.
*
* NOTE: superclass of a binary type is resolved when needed
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
index 2e7fe97..0c80de1 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
@@ -76,6 +76,8 @@
public boolean isProcessingAnnotations = false;
public boolean mayTolerateMissingType = false;
+ boolean nullAnnotationsInitialized = false;
+
final static int BUILD_FIELDS_AND_METHODS = 4;
final static int BUILD_TYPE_HIERARCHY = 1;
final static int CHECK_AND_SET_IMPORTS = 2;
@@ -83,6 +85,7 @@
static final ProblemPackageBinding TheNotFoundPackage = new ProblemPackageBinding(CharOperation.NO_CHAR, NotFound);
static final ProblemReferenceBinding TheNotFoundType = new ProblemReferenceBinding(CharOperation.NO_CHAR_CHAR, null, NotFound);
+
public LookupEnvironment(ITypeRequestor typeRequestor, CompilerOptions globalOptions, ProblemReporter problemReporter, INameEnvironment nameEnvironment) {
this.typeRequestor = typeRequestor;
@@ -723,6 +726,8 @@
* 3. Create the method bindings
*/
public PackageBinding createPackage(char[][] compoundName) {
+ if (!this.nullAnnotationsInitialized)
+ initNullAnnotationPackages();
PackageBinding packageBinding = getPackage0(compoundName[0]);
if (packageBinding == null || packageBinding == TheNotFoundPackage) {
packageBinding = new PackageBinding(compoundName[0], this);
@@ -1073,6 +1078,54 @@
return packageBinding.getType0(compoundName[compoundName.length - 1]);
}
+public char[][] getNullableAnnotationName() {
+ return this.globalOptions.nullableAnnotationName;
+}
+
+public char[][] getNonNullAnnotationName() {
+ return this.globalOptions.nonNullAnnotationName;
+}
+
+public char[][] getNonNullByDefaultAnnotationName() {
+ return this.globalOptions.nonNullByDefaultAnnotationName;
+}
+
+/**
+ * Answer the type binding representing the null-annotation identified by the given tag bits.
+ * @param annotationTagBit tag bits potentially denoting a null-annotation
+ * @param resolve should the resulting type binding be resolved?
+ * @return the corresponding annotation type binding
+ * or null
if no annotation bits are contained in the given tag bits.
+ */
+public TypeBinding getNullAnnotationBinding(long annotationTagBit, boolean resolve) {
+ char[][] name = null;
+ if (annotationTagBit == TagBits.AnnotationNonNull)
+ name = getNonNullAnnotationName();
+ else if (annotationTagBit == TagBits.AnnotationNullable)
+ name = getNullableAnnotationName();
+ else
+ return null;
+ if (resolve)
+ return getType(name);
+ else
+ return getTypeFromCompoundName(name, false, false);
+}
+
+/**
+ * Inspect the given tag bits and answer a corresponding null annotation type binding
+ * @param defaultTagBit tag bits representing the default applicable at the current code location
+ * @param resolve should the resulting type binding be resolved?
+ * @return the corresponding concrete annotation type binding (@NonNull
or @Nullable
)
+ * or null
if no bits of a default-annotation are contained in the given tag bits.
+ */
+public TypeBinding getNullAnnotationBindingFromDefault(long defaultTagBit, boolean resolve) {
+ if ((defaultTagBit & TagBits.AnnotationNullUnspecifiedByDefault) != 0)
+ return ReferenceBinding.NULL_UNSPECIFIED;
+ if ((defaultTagBit & TagBits.AnnotationNonNullByDefault) != 0)
+ return getNullAnnotationBinding(TagBits.AnnotationNonNull, resolve);
+ return null;
+}
+
/* Answer the top level package named name if it exists in the cache.
* Answer theNotFoundPackage if it could not be resolved the first time
* it was looked up, otherwise answer null.
@@ -1106,6 +1159,8 @@
* Answer null if the package cannot be found.
*/
PackageBinding getTopLevelPackage(char[] name) {
+ if (!this.nullAnnotationsInitialized)
+ initNullAnnotationPackages();
PackageBinding packageBinding = getPackage0(name);
if (packageBinding != null) {
if (packageBinding == TheNotFoundPackage)
@@ -1387,6 +1442,41 @@
}
}
+private void initNullAnnotationPackages() {
+ this.nullAnnotationsInitialized = true;
+ if (!this.globalOptions.isAnnotationBasedNullAnalysisEnabled)
+ return;
+ char[][] compoundName = getNullableAnnotationName();
+ if (compoundName != null)
+ initNullAnnotationPackage(compoundName, TypeIds.T_ConfiguredAnnotationNullable);
+ compoundName = getNonNullAnnotationName();
+ if (compoundName != null)
+ initNullAnnotationPackage(compoundName, TypeIds.T_ConfiguredAnnotationNonNull);
+ compoundName = getNonNullByDefaultAnnotationName();
+ if (compoundName != null)
+ initNullAnnotationPackage(compoundName, TypeIds.T_ConfiguredAnnotationNonNullByDefault);
+}
+
+/**
+ * Create or retrieve the package holding the specified type and store the type name.
+ */
+void initNullAnnotationPackage(char[][] typeName, int typeId) {
+ if (typeName.length < 2) {
+ this.problemReporter.nullAnnotationNameMustBeQualified(typeName);
+ return;
+ }
+ char[][] packageName = CharOperation.subarray(typeName, 0, typeName.length-1);
+ PackageBinding packageBinding = createPackage(packageName);
+ char[] simpleTypeName = typeName[typeName.length-1];
+ if (typeId == TypeIds.T_ConfiguredAnnotationNullable)
+ packageBinding.nullableName = simpleTypeName;
+ else if (typeId == TypeIds.T_ConfiguredAnnotationNonNull)
+ packageBinding.nonNullName = simpleTypeName;
+ else if (typeId == TypeIds.T_ConfiguredAnnotationNonNullByDefault)
+ packageBinding.nonNullByDefaultName = simpleTypeName;
+}
+
+
boolean isMissingType(char[] typeName) {
for (int i = this.missingTypes == null ? 0 : this.missingTypes.size(); --i >= 0;) {
MissingTypeBinding missingType = (MissingTypeBinding) this.missingTypes.get(i);
@@ -1454,6 +1544,9 @@
this.unitBeingCompleted = null; // in case AbortException occurred
this.classFilePool.reset();
+
+ this.nullAnnotationsInitialized = false;
+
// name environment has a longer life cycle, and must be reset in
// the code which created it.
}
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 33c56ef..0dce1a1 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
@@ -34,6 +34,9 @@
char[] signature;
public long tagBits;
+ /** Store nullness information from annotation (incl. applicable default). */
+ public Boolean[] parameterNonNullness; // TRUE means @NonNull declared, FALSE means @Nullable declared, null means nothing declared
+
protected MethodBinding() {
// for creating problem or synthetic method
}
@@ -439,6 +442,41 @@
return this.selector;
}
+/**
+ * After method verifier has finished, fill in missing nullness values from the applicable default.
+ * @param annotationBinding the null annotation specified to be the default at the current code location.
+ */
+protected void fillInDefaultNonNullness(TypeBinding annotationBinding) {
+ if (this.parameterNonNullness == null)
+ this.parameterNonNullness = new Boolean[this.parameters.length];
+ AbstractMethodDeclaration sourceMethod = sourceMethod();
+ for (int i = 0; i < this.parameterNonNullness.length; i++) {
+ if (this.parameters[i].isBaseType())
+ continue;
+ boolean added = false;
+ if (this.parameterNonNullness[i] == null) {
+ added = true;
+ this.parameterNonNullness[i] = Boolean.TRUE;
+ if (sourceMethod != null)
+ sourceMethod.addParameterNonNullAnnotation(i, (ReferenceBinding)annotationBinding);
+ } else if (this.parameterNonNullness[i].booleanValue()) {
+ sourceMethod.scope.problemReporter().nullAnnotationIsRedundant(sourceMethod, i);
+ }
+ if (added)
+ this.tagBits |= TagBits.HasParameterAnnotations;
+ }
+ if ( this.returnType != null
+ && !this.returnType.isBaseType()
+ && (this.tagBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) == 0)
+ {
+ this.tagBits |= TagBits.AnnotationNonNull;
+ if (sourceMethod != null)
+ sourceMethod.addNullnessAnnotation((ReferenceBinding)annotationBinding);
+ } else if ((this.tagBits & TagBits.AnnotationNonNull) != 0) {
+ sourceMethod.scope.problemReporter().nullAnnotationIsRedundant(sourceMethod, -1/*signifies method return*/);
+ }
+}
+
public MethodBinding findOriginalInheritedMethod(MethodBinding inheritedMethod) {
MethodBinding inheritedOriginal = inheritedMethod.original();
TypeBinding superType = this.declaringClass.findSuperTypeOriginatingFrom(inheritedOriginal.declaringClass);
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 9522a2c..fb559f0 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
@@ -164,6 +164,8 @@
|| this.type.superclass.erasure().findSuperTypeOriginatingFrom(originalInherited.declaringClass) == null)
this.type.addSyntheticBridgeMethod(originalInherited, concreteMethod.original());
}
+ if (!concreteMethod.isStatic() && !abstractMethod.isStatic())
+ checkNullSpecInheritance(concreteMethod, abstractMethod);
}
}
void checkForBridgeMethod(MethodBinding currentMethod, MethodBinding inheritedMethod, MethodBinding[] allInheritedMethods) {
@@ -358,6 +360,100 @@
}
return false;
+}
+void checkAgainstInheritedMethods(MethodBinding currentMethod, MethodBinding[] methods, int length, MethodBinding[] allInheritedMethods)
+{
+ super.checkAgainstInheritedMethods(currentMethod, methods, length, allInheritedMethods);
+ for (int i = length; --i >= 0;)
+ if (!currentMethod.isStatic() && !methods[i].isStatic())
+ checkNullSpecInheritance(currentMethod, methods[i]);
+}
+
+void checkNullSpecInheritance(MethodBinding currentMethod, MethodBinding inheritedMethod) {
+ 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;
+ }
+ }
+ }
+
+ // parameters:
+ Argument[] currentArguments = srcMethod == null ? null : srcMethod.arguments;
+ if (inheritedMethod.parameterNonNullness != null) {
+ // inherited method has null-annotations, check compatibility:
+
+ for (int i = 0; i < inheritedMethod.parameterNonNullness.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().parameterLackingNonNullAnnotation(
+ 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;
+ }
+ }
+ }
+ }
}
void reportRawReferences() {
@@ -932,7 +1028,7 @@
boolean isUnsafeReturnTypeOverride(MethodBinding currentMethod, MethodBinding inheritedMethod) {
// called when currentMethod's return type is NOT compatible with inheritedMethod's return type
- // JLS 3 �8.4.5: more are accepted, with an unchecked conversion
+ // JLS 3 �8.4.5: more are accepted, with an unchecked conversion
if (currentMethod.returnType == inheritedMethod.returnType.erasure()) {
TypeBinding[] currentParams = currentMethod.parameters;
TypeBinding[] inheritedParams = inheritedMethod.parameters;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java
index 0e59232..af5664d 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2009 IBM Corporation and others.
+ * Copyright (c) 2000, 2011 IBM Corporation 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
@@ -22,6 +22,12 @@
public LookupEnvironment environment;
HashtableOfType knownTypes;
HashtableOfPackage knownPackages;
+
+ protected char[] nullableName = null;
+ protected char[] nonNullName = null;
+ protected char[] nonNullByDefaultName = null;
+ protected TypeBinding nullnessDefaultAnnotation;
+
protected PackageBinding() {
// for creating problem package
}
@@ -58,6 +64,8 @@
if (this.knownTypes == null)
this.knownTypes = new HashtableOfType(25);
this.knownTypes.put(element.compoundName[element.compoundName.length - 1], element);
+ if (this.nullableName != null || this.nonNullName != null || this.nonNullByDefaultName != null)
+ setupNullAnnotationType(element);
}
void clearMissingTagBit() {
@@ -230,6 +238,26 @@
return ProblemReasons.NoError;
}
+void setupNullAnnotationType(ReferenceBinding type) {
+ int id = 0;
+ if (CharOperation.equals(this.nullableName, type.sourceName))
+ id = TypeIds.T_ConfiguredAnnotationNullable;
+ else if (CharOperation.equals(this.nonNullName, type.sourceName))
+ id = TypeIds.T_ConfiguredAnnotationNonNull;
+ else if (CharOperation.equals(this.nonNullByDefaultName, type.sourceName))
+ id = TypeIds.T_ConfiguredAnnotationNonNullByDefault;
+ else
+ return;
+
+ type.id = id; // ensure annotations of this type are detected as standard annotations.
+}
+
+public TypeBinding getNullnessDefaultAnnotation() {
+ if (this.nullnessDefaultAnnotation instanceof UnresolvedReferenceBinding)
+ return this.nullnessDefaultAnnotation = BinaryTypeBinding.resolveType(this.nullnessDefaultAnnotation, this.environment, false);
+ return this.nullnessDefaultAnnotation;
+}
+
public char[] readableName() /*java.lang*/ {
return CharOperation.concatWith(this.compoundName, '.');
}
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 1769362..e1995d1 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
@@ -48,6 +48,14 @@
public boolean hasTypeBit(int bit) { return false; }
};
+ /**
+ * This faked annotation type binding marks types with unspecified nullness.
+ * For use in {@link PackageBinding#nullnessDefaultAnnotation} and {@link SourceTypeBinding#nullnessDefaultAnnotation}
+ */
+ final static ReferenceBinding NULL_UNSPECIFIED = new ReferenceBinding() { /* faked type binding */
+ public boolean hasTypeBit(int bit) { return false; }
+ };
+
private static final Comparator FIELD_COMPARATOR = new Comparator() {
public int compare(Object o1, Object o2) {
char[] n1 = ((FieldBinding) o1).name;
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 d1f9cf4..565fdc4 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
@@ -54,6 +54,9 @@
char[] genericReferenceTypeSignature;
private SimpleLookupTable storedAnnotations = null; // keys are this ReferenceBinding & its fields and methods, value is an AnnotationHolder
+
+ private TypeBinding nullnessDefaultAnnotation;
+ private int nullnessDefaultInitialized = 0; // 0: nothing; 1: type; 2: package
public SourceTypeBinding(char[][] compoundName, PackageBinding fPackage, ClassScope scope) {
this.compoundName = compoundName;
@@ -781,6 +784,7 @@
}
if ((this.tagBits & TagBits.AnnotationDeprecated) != 0)
this.modifiers |= ClassFileConstants.AccDeprecated;
+ evaluateNullAnnotations(this.tagBits);
}
return this.tagBits;
}
@@ -1088,6 +1092,8 @@
this.modifiers |= ClassFileConstants.AccDeprecated;
}
}
+ if (CharOperation.equals(this.sourceName, TypeConstants.PACKAGE_INFO_NAME))
+ getAnnotationTagBits(); // initialize
}
// ensure the receiver knows its hierarchy & fields/methods so static imports can be resolved correctly
@@ -1590,12 +1596,103 @@
typeParameters[i].binding = null;
return null;
}
+ callBindArguments(method);
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 callBindArguments(MethodBinding method) {
+ switch (this.nullnessDefaultInitialized) {
+ case 0:
+ getAnnotationTagBits(); // initialize
+ //$FALL-THROUGH$
+ case 1:
+ getPackage().isViewedAsDeprecated(); // initialize annotations
+ this.nullnessDefaultInitialized = 2;
+ }
+ AbstractMethodDeclaration methodDecl = method.sourceMethod();
+ if (methodDecl != null) {
+ if (method.parameters != Binding.NO_PARAMETERS)
+ methodDecl.bindArguments();
+ TypeBinding annotationBinding = findDefaultNullness(method, methodDecl.scope.environment());
+ if (annotationBinding != null && annotationBinding.id == TypeIds.T_ConfiguredAnnotationNonNull)
+ method.fillInDefaultNonNullness(annotationBinding);
+ }
+}
+private void evaluateNullAnnotations(long annotationTagBits) {
+ if (this.nullnessDefaultInitialized > 0)
+ return;
+ this.nullnessDefaultInitialized = 1;
+ // transfer nullness info from tagBits to this.nullnessDefaultAnnotation
+ TypeBinding defaultAnnotation = getPackage().environment
+ .getNullAnnotationBindingFromDefault(annotationTagBits, false/*resolve*/);
+ if (defaultAnnotation != null) {
+ if (CharOperation.equals(this.sourceName, TypeConstants.PACKAGE_INFO_NAME)) {
+ getPackage().nullnessDefaultAnnotation = defaultAnnotation;
+ } else {
+ this.nullnessDefaultAnnotation = defaultAnnotation;
+ }
+ }
+}
+private TypeBinding getNullnessDefaultAnnotation() {
+ if (this.nullnessDefaultAnnotation instanceof UnresolvedReferenceBinding)
+ return this.nullnessDefaultAnnotation =
+ BinaryTypeBinding.resolveType(this.nullnessDefaultAnnotation, getPackage().environment, false);
+ return this.nullnessDefaultAnnotation;
+}
+/**
+ * Answer the nullness default applicable at the given method binding.
+ * Possible values:
+ * the type binding for @NonNulByDefault
+ * the synthetic type {@link ReferenceBinding#NULL_UNSPECIFIED} if a default from outer scope has been canceled
+ * null if no default has been defined
+ *
+ */
+private TypeBinding findDefaultNullness(MethodBinding methodBinding, LookupEnvironment environment) {
+ // find the applicable default inside->out:
+
+ // method
+ TypeBinding annotationBinding = environment.getNullAnnotationBindingFromDefault(methodBinding.tagBits, true/*resolve*/);
+ if (annotationBinding != null)
+ return annotationBinding;
+
+ // type
+ ReferenceBinding type = methodBinding.declaringClass;
+ ReferenceBinding currentType = type;
+ while (currentType instanceof SourceTypeBinding) {
+ annotationBinding = ((SourceTypeBinding) currentType).getNullnessDefaultAnnotation();
+ if (annotationBinding != null)
+ return annotationBinding;
+ currentType = currentType.enclosingType();
+ }
+
+ // package
+ annotationBinding = type.getPackage().getNullnessDefaultAnnotation();
+ if (annotationBinding != null)
+ return annotationBinding;
+
+ // global
+ long defaultNullness = environment.globalOptions.defaultNonNullness;
+ if (defaultNullness != 0) {
+ annotationBinding = environment.getNullAnnotationBinding(defaultNullness, true/*resolve*/);
+ if (annotationBinding != null)
+ return annotationBinding;
+
+ // on this branch default was not defined using an annotation, thus annotation type can still be missing
+ if (defaultNullness == TagBits.AnnotationNonNull)
+ this.scope.problemReporter().missingNullAnnotationType(environment.getNonNullAnnotationName());
+ else if (defaultNullness == TagBits.AnnotationNullable)
+ this.scope.problemReporter().missingNullAnnotationType(environment.getNullableAnnotationName());
+ else
+ this.scope.problemReporter().abortDueToInternalError("Illegal default nullness value: "+defaultNullness); //$NON-NLS-1$
+ // reset default to avoid duplicate errors:
+ environment.globalOptions.defaultNonNullness = 0;
+ }
+ return null;
+}
+
public AnnotationHolder retrieveAnnotationHolder(Binding binding, boolean forceInitialization) {
if (forceInitialization)
binding.getAnnotationTagBits(); // ensure annotations are up to date
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 55d5b88..59a6a70 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
@@ -132,6 +132,14 @@
long AnnotationPreDestroy = ASTNode.Bit54L;
/** @since 3.8 */
long AnnotationPostConstruct = ASTNode.Bit55L;
+ /** @since 3.8 null annotation for MethodBinding or LocalVariableBinding (argument): */
+ long AnnotationNullable = ASTNode.Bit56L;
+ /** @since 3.8 null annotation for MethodBinding or LocalVariableBinding (argument): */
+ long AnnotationNonNull = ASTNode.Bit57L;
+ /** @since 3.8 null-default annotation for PackageBinding or TypeBinding or MethodBinding: */
+ long AnnotationNonNullByDefault = ASTNode.Bit58L;
+ /** @since 3.8 canceling null-default annotation for PackageBinding or TypeBinding or MethodBinding: */
+ long AnnotationNullUnspecifiedByDefault = ASTNode.Bit59L;
long AllStandardAnnotationsMask =
AnnotationTargetMASK
@@ -144,10 +152,14 @@
| AnnotationSafeVarargs
| AnnotationPolymorphicSignature
| AnnotationPostConstruct
- | AnnotationPreDestroy;
+ | AnnotationPreDestroy
+ | AnnotationNullable
+ | AnnotationNonNull
+ | AnnotationNonNullByDefault
+ | AnnotationNullUnspecifiedByDefault;
- long DefaultValueResolved = ASTNode.Bit56L;
+ long DefaultValueResolved = ASTNode.Bit60L;
// set when type contains non-private constructor(s)
- long HasNonPrivateConstructor = ASTNode.Bit57L;
+ long HasNonPrivateConstructor = ASTNode.Bit61L;
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java
index 115bf83..18ab315 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java
@@ -104,6 +104,11 @@
final int T_JavaxAnnotationPostConstruct = 63;
final int T_JavaxAnnotationPreDestroy = 64;
+
+ // new in 3.8 for null annotations:
+ final int T_ConfiguredAnnotationNullable = 65;
+ final int T_ConfiguredAnnotationNonNull = 66;
+ final int T_ConfiguredAnnotationNonNullByDefault = 67;
final int NoId = Integer.MAX_VALUE;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java
index 26d3599..cbeeff7 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java
@@ -546,11 +546,11 @@
if (this.lineEnds == null || this.linePtr == -1)
return -1;
- if (lineNumber > this.lineEnds.length+1)
+ if (lineNumber > this.linePtr + 2)
return -1;
if (lineNumber <= 0)
return -1;
- if (lineNumber == this.lineEnds.length + 1)
+ if (lineNumber == this.linePtr + 2)
return this.eofPosition;
return this.lineEnds[lineNumber-1]; // next line start one character behind the lineEnd of the previous line
}
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 0a3b191..efa3712 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
@@ -84,6 +84,7 @@
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
@@ -287,6 +288,7 @@
return CompilerOptions.NullReference;
case IProblem.PotentialNullLocalVariableReference:
+ case IProblem.PotentialNullMessageSendReference:
return CompilerOptions.PotentialNullReference;
case IProblem.RedundantLocalVariableNullAssignment:
@@ -295,7 +297,24 @@
case IProblem.NonNullLocalVariableComparisonYieldsFalse:
case IProblem.NullLocalVariableComparisonYieldsFalse:
case IProblem.NullLocalVariableInstanceofYieldsFalse:
+ case IProblem.RedundantNullCheckOnNonNullMessageSend:
return CompilerOptions.RedundantNullCheck;
+
+ case IProblem.RequiredNonNullButProvidedNull:
+ case IProblem.IllegalReturnNullityRedefinition:
+ case IProblem.IllegalRedefinitionToNonNullParameter:
+ case IProblem.IllegalDefinitionToNonNullParameter:
+ case IProblem.ParameterLackingNonNullAnnotation:
+ case IProblem.ParameterLackingNullableAnnotation:
+ case IProblem.CannotImplementIncompatibleNullness:
+ return CompilerOptions.NullSpecViolation;
+
+ case IProblem.RequiredNonNullButProvidedPotentialNull:
+ return CompilerOptions.PotentialNullSpecViolation;
+ case IProblem.RequiredNonNullButProvidedUnknown:
+ return CompilerOptions.NullSpecInsufficientInfo;
+ case IProblem.RedundantNullAnnotation:
+ return CompilerOptions.RedundantNullAnnotation;
case IProblem.BoxingConversion :
case IProblem.UnboxingConversion :
@@ -542,6 +561,13 @@
case CompilerOptions.DiscouragedReference :
return CategorizedProblem.CAT_RESTRICTION;
+ case CompilerOptions.NullSpecViolation :
+ case CompilerOptions.PotentialNullSpecViolation :
+ case CompilerOptions.NullSpecInsufficientInfo :
+ return CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM;
+ case CompilerOptions.RedundantNullAnnotation :
+ return CategorizedProblem.CAT_UNNECESSARY_CODE;
+
default:
break categorizeOnIrritant;
}
@@ -550,6 +576,8 @@
switch (problemID) {
case IProblem.IsClassPathCorrect :
case IProblem.CorruptedSignature :
+ case IProblem.MissingNullAnnotationType :
+ case IProblem.NullAnnotationNameMustBeQualified :
return CategorizedProblem.CAT_BUILDPATH;
default :
@@ -7999,4 +8027,172 @@
trackVar.sourceStart,
trackVar.sourceEnd);
}
+
+public void nullityMismatch(Expression expression, TypeBinding requiredType, int nullStatus, char[][] annotationName) {
+ int problemId = IProblem.RequiredNonNullButProvidedUnknown;
+ if ((nullStatus & FlowInfo.NULL) != 0)
+ problemId = IProblem.RequiredNonNullButProvidedNull;
+ if ((nullStatus & FlowInfo.POTENTIALLY_NULL) != 0)
+ problemId = IProblem.RequiredNonNullButProvidedPotentialNull;
+ String[] arguments = new String[] {
+ String.valueOf(CharOperation.concatWith(annotationName, '.')),
+ String.valueOf(requiredType.readableName())
+ };
+ String[] argumentsShort = new String[] {
+ String.valueOf(annotationName[annotationName.length-1]),
+ String.valueOf(requiredType.shortReadableName())
+ };
+ this.handle(
+ problemId,
+ arguments,
+ argumentsShort,
+ expression.sourceStart,
+ expression.sourceEnd);
+}
+public void illegalRedefinitionToNonNullParameter(Argument argument, ReferenceBinding declaringClass, char[][] inheritedAnnotationName) {
+ int sourceStart = argument.type.sourceStart;
+ if (argument.annotations != null) {
+ for (int i=0; iThis option controls whether the compiler will use null annotations for
+ * improved analysis of (potential) null references.
+ * If enabled the compiler will interpret the annotation types defined using
+ * {@link #COMPILER_NONNULL_ANNOTATION_NAME} and {@link #COMPILER_NULLABLE_ANNOTATION_NAME}
+ * as specifying whether or not a given type includes the value null
.
+ * The effect of these analyses is further controled by the options
+ * {@link #COMPILER_PB_NULL_SPECIFICATION_VIOLATION},
+ * {@link #COMPILER_PB_POTENTIAL_NULL_SPECIFICATION_VIOLATION} and
+ * {@link #COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO}.
+ *
+ * Option id: "org.eclipse.jdt.core.compiler.annotation.nullanalysis"
+ * Possible values: { "disabled", "enabled" }
+ * Default: "disabled"
+ * @since 3.8
+ * @category CompilerOptionID
+ */
+ public static final String COMPILER_ANNOTATION_NULL_ANALYSIS = PLUGIN_ID + ".compiler.annotation.nullanalysis"; //$NON-NLS-1$
+ /**
+ * Compiler option ID: Name of Annotation Type for Nullable Types.
+ * This option defines a fully qualified Java type name that the compiler may use
+ * to perform special null analysis.
+ * If the annotation specified by this option is applied to a type in a method
+ * signature or variable declaration this will be interpreted as a specification
+ * that null
is a legal value in that position. Currently supported
+ * positions are: method parameters, method return type and local variables.
+ * If a value whose type
+ * is annotated with this annotation is dereferenced without checking for null
+ * the compiler will trigger a diagnostic as further controlled by
+ * {@link #COMPILER_PB_POTENTIAL_NULL_REFERENCE}.
+ * The compiler may furthermore check adherence to the null specification as
+ * further controlled by {@link #COMPILER_PB_NULL_SPECIFICATION_VIOLATION},
+ * {@link #COMPILER_PB_POTENTIAL_NULL_SPECIFICATION_VIOLATION} and
+ * {@link #COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO}.
+ *
+ * Option id: "org.eclipse.jdt.core.compiler.annotation.nullable"
+ * Possible values: any legal, fully qualified Java type name, must resolve to an annotation type.
+ * Default: "org.eclipse.jdt.annotation.Nullable"
+ * @since 3.8
+ * @category CompilerOptionID
+ */
+ public static final String COMPILER_NULLABLE_ANNOTATION_NAME = PLUGIN_ID + ".compiler.annotation.nullable"; //$NON-NLS-1$
+ /**
+ * Compiler option ID: Name of Annotation Type for Non-Null Types.
+ * This option defines a fully qualified Java type name that the compiler may use
+ * to perform special null analysis.
+ * If the annotation specified by this option is applied to a type in a method
+ * signature or variable declaration this will be interpreted as a specification
+ * that null
is not a legal value in that position. Currently
+ * supported positions are: method parameters, method return type and local variables.
+ * For values declared with this annotation the compiler will never trigger a null
+ * reference diagnostic (as controlled by {@link #COMPILER_PB_POTENTIAL_NULL_REFERENCE}
+ * and {@link #COMPILER_PB_NULL_REFERENCE}), because the assumption is made that null
+ * will never occur at runtime in these positions.
+ * The compiler may furthermore check adherence to the null specification as further
+ * controlled by {@link #COMPILER_PB_NULL_SPECIFICATION_VIOLATION},
+ * {@link #COMPILER_PB_POTENTIAL_NULL_SPECIFICATION_VIOLATION} and
+ * {@link #COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO}.
+ *
+ * Option id: "org.eclipse.jdt.core.compiler.annotation.nonnull"
+ * Possible values: any legal, fully qualified Java type name, must resolve to an annotation type.
+ * Default: "org.eclipse.jdt.annotation.NonNull"
+ * @since 3.8
+ * @category CompilerOptionID
+ */
+ public static final String COMPILER_NONNULL_ANNOTATION_NAME = PLUGIN_ID + ".compiler.annotation.nonnull"; //$NON-NLS-1$
+ /**
+ * Compiler option ID: Reporting Violations of Null Specifications.
+ * When enabled, the compiler will issue an error or a warning whenever one of the
+ * following situations is detected:
+ *
+ * A method declared with a nonnull annotation returns an expression that is
+ * statically known to evaluate to a null value.
+ * An expression that is statically known to evaluate to a null value is passed
+ * as an argument in a method call where the corresponding parameter of the called
+ * method is declared with a nonnull annotation.
+ * An expression that is statically known to evaluate to a null value is assigned
+ * to a local variable that is declared with a nonnull annotation.
+ * A method that overrides an inherited method declared with a nonnull annotation
+ * tries to relax that contract by specifying a nullable annotation
+ * (prohibition of contravariant return).
+ * A method that overrides an inherited method which has a nullable declaration
+ * for at least one of its parameters, tries to tighten that null contract by
+ * specifying a nonnull annotation for its corresponding parameter
+ * (prohibition of covariant parameters).
+ *
+ *
+ * The compiler options {@link #COMPILER_NONNULL_ANNOTATION_NAME} and
+ * {@link #COMPILER_NULLABLE_ANNOTATION_NAME} control which annotations the compiler
+ * shall interpret as nonnull or nullable annotations, respectively.
+ *
+ *
+ * Option id: "org.eclipse.jdt.core.compiler.problem.nullContractViolation"
+ * Possible values: { "error", "warning", "ignore" }
+ * Default: "error"
+ *
+ * @since 3.8
+ * @category CompilerOptionID
+ */
+ public static final String COMPILER_PB_NULL_SPECIFICATION_VIOLATION = PLUGIN_ID + ".compiler.problem.nullSpecViolation"; //$NON-NLS-1$
+ /**
+ * Compiler option ID: Reporting Violations of Null Specifications with Potential Null Value.
+ * When enabled, the compiler will issue an error or a warning whenever one of the
+ * following situations is detected:
+ *
+ * A method declared with a nonnull annotation returns an expression that is
+ * statically known to evaluate to a null value on some flow.
+ * An expression that is statically known to evaluate to a null value on some flow
+ * is passed as an argument in a method call where the corresponding parameter of
+ * the called method is declared with a nonnull annotation.
+ * An expression that is statically known to evaluate to a null value on some flow
+ * is assigned to a local variable that is declared with a nonnull annotation.
+ *
+ *
+ * The compiler options {@link #COMPILER_NONNULL_ANNOTATION_NAME} and
+ * {@link #COMPILER_NULLABLE_ANNOTATION_NAME} control which annotations the compiler
+ * shall interpret as nonnull or nullable annotations, respectively.
+ *
+ *
+ * Option id: "org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation"
+ * Possible values: { "error", "warning", "ignore" }
+ * Default: "error"
+ *
+ * @since 3.8
+ * @category CompilerOptionID
+ */
+ public static final String COMPILER_PB_POTENTIAL_NULL_SPECIFICATION_VIOLATION = PLUGIN_ID + ".compiler.problem.potentialNullSpecViolation"; //$NON-NLS-1$
+ /**
+ * Compiler option ID: Reporting Insufficient Information for Analysing Adherence to Null Specifications.
+ * When enabled, the compiler will issue an error or a warning whenever one of the
+ * following situations is detected:
+ *
+ * A method declared with a nonnull annotation returns an expression for which
+ * insufficient nullness information is available for statically proving that no
+ * flow will pass a null value at runtime.
+ * An expression for which insufficient nullness information is available for
+ * statically proving that it will never evaluate to a null value at runtime
+ * is passed as an argument in a method call where the corresponding parameter of
+ * the called method is declared with a nonnull annotation.
+ * An expression for which insufficient nullness information is available for
+ * statically proving that it will never evaluate to a null value at runtime
+ * is assigned to a local variable that is declared with a nonnull annotation.
+ *
+ * Insufficient nullness information is usually a consequence of using other unannotated
+ * variables or methods.
+ *
+ * The compiler options {@link #COMPILER_NONNULL_ANNOTATION_NAME} and
+ * {@link #COMPILER_NULLABLE_ANNOTATION_NAME} control which annotations the compiler
+ * shall interpret as nonnull or nullable annotations, respectively.
+ *
+ *
+ * Option id: "org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo"
+ * Possible values: { "error", "warning", "ignore" }
+ * Default: "warning"
+ *
+ * @since 3.8
+ * @category CompilerOptionID
+ */
+ public static final String COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO = PLUGIN_ID + ".compiler.problem.nullSpecInsufficientInfo"; //$NON-NLS-1$
+ /**
* Compiler option ID: Setting Source Compatibility Mode.
* Specify whether which source level compatibility is used. From 1.4 on, 'assert'
is a keyword
* reserved for assertion support. Also note, than when toggling to 1.4 mode, the target VM