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..8aa2bf0 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
@@ -14,6 +14,7 @@
* bug 185682 - Increment/decrement operators mark local variables as read
* bug 349326 - [1.7] new warning for missing try-with-resources
* bug 359721 - [options] add command line option for new warning token "resource"
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.core.tests.compiler.regression;
@@ -1799,6 +1800,11 @@
" \n" +
" \n" +
" \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
" \n" +
" \n" +
" \n" +
@@ -1857,14 +1863,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 311ded9..95fa1b1 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
@@ -11,6 +11,7 @@
* Stephan Herrmann - Contributions for
* bug 236385: [compiler] Warn for potential programming problem if an object is created but not used
* bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.core.tests.compiler.regression;
@@ -380,6 +381,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));
@@ -470,8 +472,10 @@
expectedProblemAttributes.put("HierarchyHasProblems", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("IllegalAbstractModifierCombinationForMethod", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
expectedProblemAttributes.put("IllegalAccessFromTypeVariable", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
+ expectedProblemAttributes.put("IllegalAnnotationForBaseType", 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 +510,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 +696,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));
@@ -745,6 +752,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 +780,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 +789,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);
@@ -1057,6 +1072,7 @@
expectedProblemAttributes.put("CannotExtendEnum", SKIP);
expectedProblemAttributes.put("CannotHideAnInstanceMethodWithAStaticMethod", SKIP);
expectedProblemAttributes.put("CannotImportPackage", SKIP);
+ expectedProblemAttributes.put("CannotImplementIncompatibleNullness", new ProblemAttributes(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION));
expectedProblemAttributes.put("CannotInferElidedTypes", SKIP);
expectedProblemAttributes.put("CannotInvokeSuperConstructorInEnum", SKIP);
expectedProblemAttributes.put("CannotOverrideAStaticMethodWithAnInstanceMethod", SKIP);
@@ -1146,8 +1162,10 @@
expectedProblemAttributes.put("HierarchyHasProblems", SKIP);
expectedProblemAttributes.put("IllegalAbstractModifierCombinationForMethod", SKIP);
expectedProblemAttributes.put("IllegalAccessFromTypeVariable", SKIP);
+ expectedProblemAttributes.put("IllegalAnnotationForBaseType", SKIP);
expectedProblemAttributes.put("IllegalCast", SKIP);
expectedProblemAttributes.put("IllegalClassLiteralForTypeVariable", SKIP);
+ expectedProblemAttributes.put("IllegalDefinitionToNonNullParameter", new ProblemAttributes(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION));
expectedProblemAttributes.put("IllegalDimension", SKIP);
expectedProblemAttributes.put("IllegalEnclosingInstanceSpecification", SKIP);
expectedProblemAttributes.put("IllegalExtendedDimensions", SKIP);
@@ -1182,6 +1200,8 @@
expectedProblemAttributes.put("IllegalPrimitiveOrArrayTypeForEnclosingInstance", SKIP);
expectedProblemAttributes.put("IllegalQualifiedEnumConstantLabel", SKIP);
expectedProblemAttributes.put("IllegalQualifiedParameterizedTypeAllocation", SKIP);
+ expectedProblemAttributes.put("IllegalRedefinitionToNonNullParameter", new ProblemAttributes(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION));
+ expectedProblemAttributes.put("IllegalReturnNullityRedefinition", new ProblemAttributes(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION));
expectedProblemAttributes.put("IllegalStaticModifierForMemberType", SKIP);
expectedProblemAttributes.put("IllegalTypeVariableSuperReference", SKIP);
expectedProblemAttributes.put("IllegalUnderscorePosition", SKIP);
@@ -1365,6 +1385,7 @@
expectedProblemAttributes.put("MissingEnclosingInstance", SKIP);
expectedProblemAttributes.put("MissingEnclosingInstanceForConstructorCall", SKIP);
expectedProblemAttributes.put("MissingEnumConstantCase", new ProblemAttributes(JavaCore.COMPILER_PB_INCOMPLETE_ENUM_SWITCH));
+ expectedProblemAttributes.put("MissingNullAnnotationType", SKIP);
expectedProblemAttributes.put("MissingOverrideAnnotation", new ProblemAttributes(JavaCore.COMPILER_PB_MISSING_OVERRIDE_ANNOTATION));
expectedProblemAttributes.put("MissingOverrideAnnotationForInterfaceMethodImplementation", new ProblemAttributes(JavaCore.COMPILER_PB_MISSING_OVERRIDE_ANNOTATION));
expectedProblemAttributes.put("MissingReturnType", SKIP);
@@ -1421,6 +1442,8 @@
expectedProblemAttributes.put("PackageCollidesWithType", SKIP);
expectedProblemAttributes.put("PackageIsNotExpectedPackage", SKIP);
expectedProblemAttributes.put("ParameterAssignment", new ProblemAttributes(JavaCore.COMPILER_PB_PARAMETER_ASSIGNMENT));
+ expectedProblemAttributes.put("ParameterLackingNonNullAnnotation", new ProblemAttributes(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION));
+ expectedProblemAttributes.put("ParameterLackingNullableAnnotation", new ProblemAttributes(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION));
expectedProblemAttributes.put("ParameterMismatch", SKIP);
expectedProblemAttributes.put("ParameterizedConstructorArgumentTypeMismatch", SKIP);
expectedProblemAttributes.put("ParameterizedMethodArgumentTypeMismatch", SKIP);
@@ -1447,6 +1470,7 @@
expectedProblemAttributes.put("PotentiallyUnclosedCloseable", new ProblemAttributes(JavaCore.COMPILER_PB_POTENTIALLY_UNCLOSED_CLOSEABLE));
expectedProblemAttributes.put("PotentiallyUnclosedCloseableAtExit", new ProblemAttributes(JavaCore.COMPILER_PB_POTENTIALLY_UNCLOSED_CLOSEABLE));
expectedProblemAttributes.put("PotentialNullLocalVariableReference", new ProblemAttributes(JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE));
+ expectedProblemAttributes.put("PotentialNullMessageSendReference", new ProblemAttributes(JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE));
expectedProblemAttributes.put("PublicClassMustMatchFileName", SKIP);
expectedProblemAttributes.put("RawMemberTypeCannotBeParameterized", SKIP);
expectedProblemAttributes.put("RawTypeReference", new ProblemAttributes(JavaCore.COMPILER_PB_RAW_TYPE_REFERENCE));
@@ -1455,11 +1479,16 @@
expectedProblemAttributes.put("RedefinedLocal", SKIP);
expectedProblemAttributes.put("RedundantSpecificationOfTypeArguments", new ProblemAttributes(JavaCore.COMPILER_PB_REDUNDANT_TYPE_ARGUMENTS));
expectedProblemAttributes.put("RedundantLocalVariableNullAssignment", new ProblemAttributes(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK));
+ expectedProblemAttributes.put("RedundantNullAnnotation", new ProblemAttributes(JavaCore.COMPILER_PB_REDUNDANT_NULL_ANNOTATION));
expectedProblemAttributes.put("RedundantNullCheckOnNonNullLocalVariable", new ProblemAttributes(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK));
+ expectedProblemAttributes.put("RedundantNullCheckOnNonNullMessageSend", new ProblemAttributes(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK));
expectedProblemAttributes.put("RedundantNullCheckOnNullLocalVariable", new ProblemAttributes(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK));
expectedProblemAttributes.put("RedundantSuperinterface", new ProblemAttributes(JavaCore.COMPILER_PB_REDUNDANT_SUPERINTERFACE));
expectedProblemAttributes.put("ReferenceToForwardField", SKIP);
expectedProblemAttributes.put("ReferenceToForwardTypeVariable", SKIP);
+ expectedProblemAttributes.put("RequiredNonNullButProvidedNull", new ProblemAttributes(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION));
+ expectedProblemAttributes.put("RequiredNonNullButProvidedPotentialNull", new ProblemAttributes(JavaCore.COMPILER_PB_POTENTIAL_NULL_SPECIFICATION_VIOLATION));
+ expectedProblemAttributes.put("RequiredNonNullButProvidedUnknown", new ProblemAttributes(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO));
expectedProblemAttributes.put("ResourceHasToImplementAutoCloseable", SKIP);
expectedProblemAttributes.put("ReturnTypeAmbiguous", SKIP);
expectedProblemAttributes.put("ReturnTypeCannotBeVoidArray", SKIP);
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java
new file mode 100644
index 0000000..491da01
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java
@@ -0,0 +1,2214 @@
+/*******************************************************************************
+ * 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.core.JavaCore;
+
+// see bug 186342 - [compiler][null] Using annotations for null checking
+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_illegal_annotation_00" };
+// 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(JavaCore.COMPILER_PB_NULL_REFERENCE, JavaCore.ERROR);
+ defaultOptions.put(JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE, JavaCore.ERROR);
+ defaultOptions.put(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK, JavaCore.ERROR);
+ defaultOptions.put(JavaCore.COMPILER_PB_INCLUDE_ASSERTS_IN_NULL_ANALYSIS, JavaCore.ENABLED);
+
+ defaultOptions.put(JavaCore.COMPILER_PB_MISSING_OVERRIDE_ANNOTATION_FOR_INTERFACE_METHOD_IMPLEMENTATION, JavaCore.DISABLED);
+
+ // enable null annotations:
+ defaultOptions.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+ // leave other new options at these defaults:
+// defaultOptions.put(CompilerOptions.OPTION_ReportNullContractViolation, JavaCore.ERROR);
+// defaultOptions.put(CompilerOptions.OPTION_ReportPotentialNullContractViolation, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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 nullable 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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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 non-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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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 non-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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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 X cannot implement the corresponding method from 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 X cannot implement the corresponding method from 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 X cannot implement the corresponding method from 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 X cannot implement the corresponding method from 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() {
+ 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"
+ },
+ null, // options
+ "");
+}
+//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(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK, JavaCore.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(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK, JavaCore.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(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.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(JavaCore.COMPILER_PB_SUPPRESS_OPTIONAL_ERRORS, JavaCore.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(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "org.foo.Nullable");
+ customOptions.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "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(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "org.foo.Nullable");
+ customOptions.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "org.foo.MayBeNull");
+ customOptions.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "org.foo.MayBeNull");
+ customOptions.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "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(JavaCore.COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "org.foo.MayBeNull");
+ customOptions.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "org.foo.MustNotBeNull");
+ customOptions.put(JavaCore.COMPILER_NONNULL_IS_DEFAULT, JavaCore.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
+// disabled because specific error is not currently raised
+public void _test_illegal_annotation_002() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "NichtNull");
+ runNegativeTestWithLibs(
+ new String[] {
+ "X.java",
+ "public class X {\n" +
+ "}\n"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in X.java (at line 1)\n" +
+ " public class X {\n" +
+ " ^\n" +
+ "Cannot use the unqualified name \'NichtNull\' as an annotation name for null specification\n" +
+ "----------\n");
+}
+
+// a null annotation is illegally used on a void method:
+public void test_illegal_annotation_003() {
+ runNegativeTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " @NonNull void foo() {}\n" +
+ "}\n"
+ },
+ "----------\n" +
+ "1. ERROR in X.java (at line 3)\n" +
+ " @NonNull void foo() {}\n" +
+ " ^^^^^^^^^^^^^\n" +
+ "The nullness annotation @NonNull is not applicable for the primitive type void\n" +
+ "----------\n",
+ this.LIBS,
+ false/*shouldFlush*/);
+}
+
+// a null annotation is illegally used on a primitive type parameter
+public void test_illegal_annotation_004() {
+ runNegativeTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(@Nullable int i) {}\n" +
+ "}\n"
+ },
+ "----------\n" +
+ "1. ERROR in X.java (at line 3)\n" +
+ " void foo(@Nullable int i) {}\n" +
+ " ^^^^^^^^^^^^^\n" +
+ "The nullness annotation @Nullable is not applicable for the primitive type int\n" +
+ "----------\n",
+ this.LIBS,
+ false/*shouldFlush*/);
+}
+
+// a null annotation is illegally used on a primitive type local var
+public void test_illegal_annotation_005() {
+ runNegativeTest(
+ new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " int foo() {\n" +
+ " @Nullable int i = 3;\n" +
+ " return i;\n" +
+ " }\n" +
+ "}\n"
+ },
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " @Nullable int i = 3;\n" +
+ " ^^^^^^^^^^^^^\n" +
+ "The nullness annotation @Nullable is not applicable for the primitive type int\n" +
+ "----------\n",
+ this.LIBS,
+ false/*shouldFlush*/);
+}
+
+// a configured annotation type does not exist
+// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=186342#c133
+public void test_illegal_annotation_006() {
+ Map customOptions = getCompilerOptions();
+ customOptions.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "nullAnn.Nullable");
+ runNegativeTestWithLibs(
+ new String[] {
+ "p/Test.java",
+ "package p;\n" +
+ "import nullAnn.*; // 1 \n" +
+ "\n" +
+ "public class Test { \n" +
+ "\n" +
+ " void foo(@nullAnn.Nullable Object o) { // 2\n" +
+ " o.toString(); \n" +
+ " }\n" +
+ "}"
+ },
+ customOptions,
+ "----------\n" +
+ "1. ERROR in p\\Test.java (at line 2)\n" +
+ " import nullAnn.*; // 1 \n" +
+ " ^^^^^^^\n" +
+ "The import nullAnn cannot be resolved\n" +
+ "----------\n" +
+ "2. ERROR in p\\Test.java (at line 6)\n" +
+ " void foo(@nullAnn.Nullable Object o) { // 2\n" +
+ " ^^^^^^^\n" +
+ "nullAnn cannot be resolved to a type\n" +
+ "----------\n");
+}
+
+public void test_default_nullness_002() {
+ Map customOptions = getCompilerOptions();
+// customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NONNULL_IS_DEFAULT, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "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_ReportPotentialNullSpecViolation, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "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_ReportPotentialNullSpecViolation, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NONNULL_IS_DEFAULT, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NONNULL_IS_DEFAULT, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NONNULL_IS_DEFAULT, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NONNULL_IS_DEFAULT, JavaCore.ENABLED);
+ customOptions.put(JavaCore.COMPILER_PB_REDUNDANT_NULL_ANNOTATION, JavaCore.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_ReportPotentialNullSpecViolation, JavaCore.ERROR);
+ customOptions.put(JavaCore.COMPILER_NONNULL_IS_DEFAULT, JavaCore.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..4dbf8ba 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.core.tests.compiler.regression;
@@ -96,6 +97,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..1e06c11 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.core.tests.dom;
@@ -7494,7 +7495,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..e89ba8f 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
@@ -10,6 +10,7 @@
* Stephan Herrmann - Contributions for
* Bug 342671 - ClassCastException: org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding cannot be cast to org.eclipse.jdt.internal.compiler.lookup.ArrayBinding
* Bug 353474 - type converters should include more annotations
+ * Bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.core.tests.dom;
@@ -7497,7 +7498,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/model/AllJavaModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
index d3e6b2b..537f730 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.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
@@ -185,6 +185,9 @@
UtilTests.class,
JavaCoreOptionsTests.class,
+
+ // Tests regarding null-annotations:
+ NullAnnotationModelTests.class,
};
Class[] deprecatedClasses = getDeprecatedJDOMTestClasses();
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java
new file mode 100644
index 0000000..51a6b88
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java
@@ -0,0 +1,418 @@
+/*******************************************************************************
+ * Copyright (c) 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.model;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringBufferInputStream;
+import java.net.URL;
+import java.util.Hashtable;
+
+import junit.framework.Test;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaModelMarker;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+
+public class NullAnnotationModelTests extends ReconcilerTests {
+
+ String ANNOTATION_LIB;
+
+ public static Test suite() {
+ return buildModelTestSuite(NullAnnotationModelTests.class);
+ }
+
+ public NullAnnotationModelTests(String name) {
+ super(name);
+ }
+
+ static {
+// TESTS_NAMES = new String[] { "testMissingAnnotation5" };
+ }
+
+ public void setUp() throws Exception {
+ super.setUp();
+ File bundleFile = FileLocator.getBundleFile(Platform.getBundle("org.eclipse.jdt.annotation.null"));
+ this.ANNOTATION_LIB = bundleFile.isDirectory() ? bundleFile.getPath()+"/bin" : bundleFile.getPath();
+ }
+
+ protected String testJarPath(String jarName) throws IOException {
+ URL libEntry = Platform.getBundle("org.eclipse.jdt.core.tests.model").getEntry("/workspace/NullAnnotations/lib/"+jarName);
+ return FileLocator.toFileURL(libEntry).getPath();
+ }
+
+
+ public void testConvertedSourceType1() throws CoreException, InterruptedException {
+ try {
+ // Resources creation
+ IJavaProject p = createJavaProject("P", new String[] {""}, new String[] {"JCL15_LIB", this.ANNOTATION_LIB}, "bin", "1.5");
+ p.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+ p.setOption(JavaCore.COMPILER_NONNULL_IS_DEFAULT, JavaCore.ENABLED);
+
+ this.createFolder("/P/p1");
+ String c1SourceString =
+ "package p1;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class C1 {\n" +
+ " public String foo(@Nullable Object arg) {\n" + // this is consumed via SourceTypeConverter
+ " return arg == null ? \"\" : arg.toString();\n" +
+ " }\n" +
+ "}\n";
+ this.createFile(
+ "/P/p1/C1.java",
+ c1SourceString);
+
+ this.createFolder("/P/p2");
+ String c2SourceString =
+ "package p2;\n" +
+ "public class C2 {\n" +
+ " String bar(p1.C1 c, C2 c2) {;\n" +
+ " return c.foo(null);\n" + // don't complain despite default nonnull, foo has explicit @Nullable
+ " }\n" +
+ " String foo(Object arg) {\n" +
+ " return arg == null ? null : arg.toString();\n" +
+ " }\n" +
+ "}\n";
+ this.createFile(
+ "/P/p2/C2.java",
+ c2SourceString);
+
+ char[] c2SourceChars = c2SourceString.toCharArray();
+ this.problemRequestor.initialize(c2SourceChars);
+
+ getCompilationUnit("/P/p2/C2.java").getWorkingCopy(this.wcOwner, null);
+
+ assertProblems("Unexpected problems", "----------\n" +
+ "1. WARNING in /P/p2/C2.java (at line 7)\n" +
+ " return arg == null ? null : arg.toString();\n" +
+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" +
+ "Potential type mismatch: required \'@NonNull String\' but nullness of the provided value is unknown\n" +
+ "----------\n");
+ } finally {
+ deleteProject("P");
+ }
+ }
+
+ public void testBinaryType1() throws CoreException, InterruptedException, IOException {
+ try {
+ // Resources creation
+ IJavaProject p = createJavaProject("P", new String[] {""},
+ new String[] {"JCL15_LIB", this.ANNOTATION_LIB, testJarPath("example.jar")},
+ "bin", "1.5");
+ p.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+ p.setOption(JavaCore.COMPILER_NONNULL_IS_DEFAULT, JavaCore.ENABLED);
+
+ // example.jar contains p1/C1.java just like testConvertedSourceType1()
+
+ this.createFolder("/P/p2");
+ String c2SourceString =
+ "package p2;\n" +
+ "public class C2 {\n" +
+ " String bar(p1.C1 c) {;\n" +
+ " return c.foo(null);\n" + // don't complain despite default nonnull, foo has explicit @Nullable
+ " }\n" +
+ " String foo(Object arg) {\n" +
+ " return arg == null ? null : arg.toString();\n" +
+ " }\n" +
+ "}\n";
+ this.createFile(
+ "/P/p2/C2.java",
+ c2SourceString);
+
+ char[] c2SourceChars = c2SourceString.toCharArray();
+ this.problemRequestor.initialize(c2SourceChars);
+
+ getCompilationUnit("/P/p2/C2.java").getWorkingCopy(this.wcOwner, null);
+
+ assertProblems("Unexpected problems", "----------\n" +
+ "1. WARNING in /P/p2/C2.java (at line 7)\n" +
+ " return arg == null ? null : arg.toString();\n" +
+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" +
+ "Potential type mismatch: required \'@NonNull String\' but nullness of the provided value is unknown\n" +
+ "----------\n");
+ } finally {
+ deleteProject("P");
+ }
+ }
+
+ public void testMissingAnnotation1() throws CoreException {
+ try {
+ // Resources creation
+ IJavaProject p = createJavaProject("P", new String[] {""}, new String[] {"JCL15_LIB", this.ANNOTATION_LIB}, "bin", "1.5");
+ p.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+ p.setOption(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "in.valid");
+
+ this.createFolder("/P/p1");
+ String c1SourceString =
+ "package p1;\n" +
+ "@org.eclipse.jdt.annotation.NonNullByDefault\n" +
+ "public class C1 {\n" +
+ " public String foo(Object arg) {\n" +
+ " return arg == null ? \"\" : arg.toString();\n" +
+ " }\n" +
+ "}\n";
+ this.createFile(
+ "/P/p1/C1.java",
+ c1SourceString);
+
+ this.problemRequestor.initialize(c1SourceString.toCharArray());
+
+ getCompilationUnit("/P/p1/C1.java").getWorkingCopy(this.wcOwner, null);
+
+ assertProblems("Unexpected problems",
+ "----------\n" +
+ "1. ERROR in /P/p1/C1.java (at line 1)\n" +
+ " package p1;\n" +
+ " ^\n" +
+ "Buildpath problem: the type in.valid, which is configured as a null annotation type, cannot be resolved\n" +
+ "----------\n");
+ } finally {
+ deleteProject("P");
+ }
+ }
+
+ public void testMissingAnnotation2() throws CoreException {
+ Hashtable javaOptions = JavaCore.getOptions();
+ try {
+ // Resources creation
+ IJavaProject p = createJavaProject("P", new String[] {""}, new String[] {"JCL15_LIB", this.ANNOTATION_LIB}, "bin", "1.5");
+ IFile settings = (IFile) p.getProject().findMember(".settings/org.eclipse.jdt.core.prefs");
+ settings.appendContents(new StringBufferInputStream("\norg.eclipse.jdt.core.compiler.annotation.nonnull=not.valid\n"), 0, null);
+ p.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+
+ this.createFolder("/P/p1");
+ String c1SourceString =
+ "package p1;\n" +
+ "@org.eclipse.jdt.annotation.NonNullByDefault\n" +
+ "public class C1 {\n" +
+ " public String foo(Object arg) {\n" +
+ " return arg == null ? \"\" : arg.toString();\n" +
+ " }\n" +
+ "}\n";
+ this.createFile(
+ "/P/p1/C1.java",
+ c1SourceString);
+
+ this.problemRequestor.initialize(c1SourceString.toCharArray());
+
+ getCompilationUnit("/P/p1/C1.java").getWorkingCopy(this.wcOwner, null);
+
+ assertProblems("Unexpected problems",
+ "----------\n" +
+ "1. ERROR in /P/p1/C1.java (at line 1)\n" +
+ " package p1;\n" +
+ " ^\n" +
+ "Buildpath problem: the type not.valid, which is configured as a null annotation type, cannot be resolved\n" +
+ "----------\n");
+ } finally {
+ deleteProject("P");
+ JavaCore.setOptions(javaOptions);
+ // work against side-effect of JavaRuntime listening to change of prefs-file.
+ // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=302850#c25
+ }
+ }
+
+ // Bug 363858 - [dom] early throwing of AbortCompilation causes NPE in CompilationUnitResolver
+ // currently not actually challenging the NPE, because we no longer report
+ // "Cannot use the unqualified name \'invalid\' as an annotation name for null specification"
+ public void testMissingAnnotation3() throws CoreException {
+ try {
+ // Resources creation
+ IJavaProject p = createJavaProject("P", new String[] {""}, new String[] {"JCL15_LIB", this.ANNOTATION_LIB}, "bin", "1.5");
+ p.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+ p.setOption(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "invalid");
+
+ this.createFolder("/P/p1");
+ String c1SourceString =
+ "package p1;\n" +
+ "@org.eclipse.jdt.annotation.NonNullByDefault\n" +
+ "public class C1 {\n" +
+ " public String foo(Object arg) {\n" +
+ " return arg == null ? \"\" : arg.toString();\n" +
+ " }\n" +
+ "}\n";
+ this.createFile(
+ "/P/p1/C1.java",
+ c1SourceString);
+
+ this.problemRequestor.initialize(c1SourceString.toCharArray());
+
+ final ICompilationUnit unit = getCompilationUnit("/P/p1/C1.java").getWorkingCopy(this.wcOwner, null);
+ assertProblems("Unexpected problems",
+ "----------\n" +
+ "1. ERROR in /P/p1/C1.java (at line 1)\n" +
+ " package p1;\n" +
+ " ^\n" +
+ "Buildpath problem: the type invalid, which is configured as a null annotation type, cannot be resolved\n" +
+ "----------\n");
+
+ ASTParser parser = ASTParser.newParser(AST.JLS4);
+ parser.setProject(p);
+ parser.setResolveBindings(true);
+ parser.setSource(unit);
+ CompilationUnit ast = (CompilationUnit) parser.createAST(null);
+ assertNotNull("ast should not be null", ast);
+ this.problemRequestor.reset();
+ this.problemRequestor.beginReporting();
+ IProblem[] problems = ast.getProblems();
+ for (int i=0; i - Contribution for bug 185682 - Increment/decrement operators mark local variables as read
+ * Stephan Herrmann - Contributions for
+ * bug 185682 - Increment/decrement operators mark local variables as read
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -253,6 +255,11 @@
// 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;
public ASTNode() {
@@ -601,7 +608,7 @@
local.tagBits |= (TagBits.AnnotationResolved | TagBits.DeprecatedAnnotationResolved);
if (length > 0) {
annotations = new AnnotationBinding[length];
- local.setAnnotations(annotations);
+ local.setAnnotations(annotations, scope);
}
break;
default :
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..f6d98fd 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
@@ -7,11 +7,15 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
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.*;
@@ -64,6 +68,61 @@
throw new AbortType(this.compilationResult, problem);
default :
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;
+ }
+
+ /**
+ * When a method is accessed via SourceTypeBinding.resolveTypesFor(MethodBinding)
+ * we create the argument binding and resolve annotations in order to compute null annotation tagbits.
+ */
+ public void createArgumentBindings() {
+ if (this.arguments != null && this.binding != null) {
+ for (int i = 0, length = this.arguments.length; i < length; i++) {
+ Argument argument = this.arguments[i];
+ argument.createBinding(this.scope, this.binding.parameters[i]);
+ // createBinding() has resolved annotations, now transfer nullness info from the argument to the method:
+ if ((argument.binding.tagBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) != 0) {
+ if (this.binding.parameterNonNullness == null)
+ this.binding.parameterNonNullness = new Boolean[this.arguments.length];
+ this.binding.parameterNonNullness[i] = Boolean.valueOf((argument.binding.tagBits & TagBits.AnnotationNonNull) != 0);
+ }
+ }
}
}
@@ -139,6 +198,28 @@
}
}
}
+ }
+ }
+ }
+
+ /**
+ * Feed null information from argument annotations into the analysis and mark arguments as assigned.
+ */
+ void analyseArguments(FlowInfo flowInfo) {
+ 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);
}
}
}
@@ -415,6 +496,7 @@
bindThrownExceptions();
resolveJavadoc();
resolveAnnotations(this.scope, this.annotations, this.binding);
+ validateAnnotations();
resolveStatements();
// check @Deprecated annotation presence
if (this.binding != null
@@ -478,4 +560,17 @@
public TypeParameter[] typeParameters() {
return null;
}
+
+ void validateAnnotations() {
+ // null annotations on parameters?
+ if (this.binding != null && this.binding.parameterNonNullness != null) {
+ for (int i=0; i - Contributions for
+ * Stephan Herrmann - Contributions for
* bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE
* bug 292478 - Report potentially null across variable assignment
* bug 335093 - [compiler][null] minimal hook for future null annotation support
* bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -65,7 +66,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..05659ad 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
@@ -7,9 +7,10 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contributions for
+ * Stephan Herrmann - Contributions for
* bug 343713 - [compiler] bogus line number in constructor of inner class in 1.5 compliance
* bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -113,12 +114,8 @@
}
}
- // tag parameters as being set
- if (this.arguments != null) {
- for (int i = 0, count = this.arguments.length; i < count; i++) {
- flowInfo.markAsDefinitelyAssigned(this.arguments[i].binding);
- }
- }
+ // nullity and mark as assigned
+ analyseArguments(flowInfo);
// propagate to constructor call
if (this.constructorCall != null) {
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..851cd6e 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -23,6 +24,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..6f47e40 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
@@ -7,7 +7,9 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contribution for bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE
+ * Stephan Herrmann - Contributions for
+ * bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -81,6 +83,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 2d0066b..bbd3cfa 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
@@ -7,11 +7,12 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contributions for
+ * Stephan Herrmann - Contributions for
* bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE
* bug 292478 - Report potentially null across variable assignment
* bug 335093 - [compiler][null] minimal hook for future null annotation support
* bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -85,7 +86,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
@@ -250,6 +251,7 @@
}
// only resolve annotation at the end, for constant to be positioned before (96991)
resolveAnnotations(scope, this.annotations, this.binding);
+ scope.validateNullAnnotation(this.binding.tagBits, this.type, this.annotations);
}
public void traverse(ASTVisitor visitor, BlockScope scope) {
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..0902e21 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -41,6 +42,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..1d8c153 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
@@ -11,6 +11,7 @@
* Stephan Herrmann - Contributions for
* bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE
* bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -99,6 +100,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 +116,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 +275,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..0e22139 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
@@ -7,7 +7,9 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contribution for bug 349326 - [1.7] new warning for missing try-with-resources
+ * Stephan Herrmann - Contributions for
+ * bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -79,10 +81,11 @@
this.scope,
FlowInfo.DEAD_END);
- // tag parameters as being set
+ // nullity and mark as assigned
+ analyseArguments(flowInfo);
+
if (this.arguments != null) {
for (int i = 0, count = this.arguments.length; i < count; i++) {
- 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
if (this.arguments[i].binding != null && (this.arguments[i].binding.type instanceof TypeVariableBinding)) {
@@ -307,4 +310,11 @@
public TypeParameter[] typeParameters() {
return this.typeParameters;
}
+
+ void validateAnnotations() {
+ super.validateAnnotations();
+ // null-annotations on the return type?
+ if (this.binding != null)
+ this.scope.validateNullAnnotation(this.binding.tagBits, this.returnType, this.annotations);
+ }
}
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..3a2a0ed 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
@@ -10,6 +10,7 @@
* Stephan Herrmann - Contributions for
* bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE
* bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -86,6 +87,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..330cafd 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
@@ -7,7 +7,9 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contribution for bug 185682 - Increment/decrement operators mark local variables as read
+ * Stephan Herrmann - Contributions for
+ * bug 185682 - Increment/decrement operators mark local variables as read
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -963,7 +965,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..57f4d89 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
@@ -7,10 +7,11 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contributions for
+ * Stephan Herrmann - Contributions for
* bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE
* bug 349326 - [1.7] new warning for missing try-with-resources
* bug 360328 - [compiler][null] detect null problems in nested code (local class inside a loop)
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -44,6 +45,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 +122,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 c723aff..9114e38 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
@@ -7,9 +7,10 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contributions for
+ * Stephan Herrmann - Contributions for
* bug 335093 - [compiler][null] minimal hook for future null annotation support
* bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
@@ -57,8 +58,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..d148222 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow;
@@ -17,6 +18,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 +85,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 +172,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 +456,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..7df8a71 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
@@ -4,10 +4,12 @@
* 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:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contribution for bug 358827 - [1.7] exception analysis for t-w-r spoils null analysis
+ * Stephan Herrmann - Contributions for
+ * bug 358827 - [1.7] exception analysis for t-w-r spoils null analysis
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow;
@@ -52,6 +54,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 +72,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 +556,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 +769,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..38295c8 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
@@ -7,11 +7,14 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - contribution for Bug 336428 - [compiler][null] bogus warning "redundant null check" in condition of do {} while() loop
+ * Stephan Herrmann - contributions for
+ * bug 336428 - [compiler][null] bogus warning "redundant null check" in condition of do {} while() loop
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
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 +24,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 +250,9 @@
continue;
}
break;
+ case ASSIGN_TO_NONNULL:
+ this.parent.recordNullityMismatch(scope, expression, flowInfo.nullStatus(local), this.expectedTypes[i]);
+ break;
default:
// never happens
}
@@ -337,6 +344,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 +681,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..b949de4 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
@@ -12,6 +12,7 @@
* bug 236385 - [compiler] Warn for potential programming problem if an object is created but not used
* bug 295551 - Add option to automatically promote all warnings to errors
* bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.impl;
@@ -26,6 +27,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 +144,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 +260,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 +388,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 +597,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 +771,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 +841,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 +1058,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 +1220,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 +1508,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..e8ae215 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
@@ -7,7 +7,9 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contribution for bug 349326 - [1.7] new warning for missing try-with-resources
+ * Stephan Herrmann - Contributions for
+ * bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.impl;
@@ -102,8 +104,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 +119,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..c296801 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
@@ -7,7 +7,9 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - Contribution for bug 349326 - [1.7] new warning for missing try-with-resources
+ * Stephan Herrmann - Contributions for
+ * bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
@@ -16,9 +18,11 @@
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;
+import org.eclipse.jdt.internal.compiler.util.Util;
/*
Not all fields defined by this type are initialized when it is created.
@@ -383,6 +387,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 +595,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 +1151,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] != Util.C_RESOLVED)
+ 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] != Util.C_RESOLVED)
+ 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] != Util.C_RESOLVED)
+ 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/LocalVariableBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalVariableBinding.java
index b163168..5f6ea20 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalVariableBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalVariableBinding.java
@@ -4,12 +4,13 @@
* 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:
* IBM Corporation - initial API and implementation
* Stephan Herrmann - Contributions for
* bug 185682 - Increment/decrement operators mark local variables as read
* bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
@@ -158,7 +159,7 @@
annotations = new AnnotationBinding[length];
for (int i = 0; i < length; i++)
annotations[i] = new AnnotationBinding(annotationNodes[i]);
- setAnnotations(annotations);
+ setAnnotations(annotations, this.declaringScope);
}
}
}
@@ -212,10 +213,12 @@
this.initializationCount++;
}
- public void setAnnotations(AnnotationBinding[] annotations) {
- if (this.declaringScope == null) return;
-
- SourceTypeBinding sourceType = this.declaringScope.enclosingSourceType();
+ public void setAnnotations(AnnotationBinding[] annotations, Scope scope) {
+ // note: we don's use this.declaringScope because we might be called before Scope.addLocalVariable(this)
+ // which is where this.declaringScope is set.
+ if (scope == null)
+ return;
+ SourceTypeBinding sourceType = scope.enclosingSourceType();
if (sourceType != null)
sourceType.storeAnnotations(this, annotations);
}
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..659ef81 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
@@ -7,7 +7,9 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
- * Stephan Herrmann - contribution for bug 337868 - [compiler][model] incomplete support for package-info.java when using SearchableEnvironment
+ * Stephan Herrmann - contributions for
+ * bug 337868 - [compiler][model] incomplete support for package-info.java when using SearchableEnvironment
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
@@ -76,6 +78,10 @@
public boolean isProcessingAnnotations = false;
public boolean mayTolerateMissingType = false;
+ PackageBinding nullableAnnotationPackage; // the package supposed to contain the Nullable annotation type
+ PackageBinding nonnullAnnotationPackage; // the package supposed to contain the NonNull annotation type
+ PackageBinding nonnullByDefaultAnnotationPackage; // the package supposed to contain the NonNullByDefault annotation type
+
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 +89,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;
@@ -1073,6 +1080,73 @@
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;
+}
+
+TypeBinding getNullAnnotationResolved(TypeBinding nullAnnotation, Scope scope) {
+ // avoid unspecific error "The type in.valid cannot be resolved. It is indirectly referenced from required .class files"
+ boolean tolerateMissing = this.mayTolerateMissingType;
+ this.mayTolerateMissingType = true;
+ try {
+ int id = nullAnnotation.id;
+ nullAnnotation = BinaryTypeBinding.resolveType(nullAnnotation, this, false);
+ nullAnnotation.id = id;
+ } finally {
+ this.mayTolerateMissingType = tolerateMissing;
+ }
+ if (nullAnnotation instanceof MissingTypeBinding) {
+ // convert error into a specific one:
+ scope.problemReporter().missingNullAnnotationType(((MissingTypeBinding)nullAnnotation).compoundName);
+ return null;
+ }
+ return nullAnnotation;
+}
+
/* 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.
@@ -1454,6 +1528,7 @@
this.unitBeingCompleted = null; // in case AbortException occurred
this.classFilePool.reset();
+
// 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..0e1140d 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
@@ -4,9 +4,10 @@
* 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:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
@@ -33,6 +34,9 @@
public TypeVariableBinding[] typeVariables = Binding.NO_TYPE_VARIABLES;
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 +443,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 3b0cae1..a9da269 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
@@ -165,6 +166,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) {
@@ -359,6 +362,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() {
@@ -935,7 +1032,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..8d7c02d 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
@@ -22,6 +23,10 @@
public LookupEnvironment environment;
HashtableOfType knownTypes;
HashtableOfPackage knownPackages;
+
+ // annotation type binding representing the default that has been defined for this package (using @NonNullByDefault)
+ protected TypeBinding nullnessDefaultAnnotation;
+
protected PackageBinding() {
// for creating problem package
}
@@ -36,6 +41,8 @@
this.environment = environment;
this.knownTypes = null; // initialized if used... class counts can be very large 300-600
this.knownPackages = new HashtableOfPackage(3); // sub-package counts are typically 0-3
+ if (compoundName != CharOperation.NO_CHAR_CHAR)
+ checkIfNullAnnotationPackage();
}
public PackageBinding(LookupEnvironment environment) {
@@ -58,6 +65,8 @@
if (this.knownTypes == null)
this.knownTypes = new HashtableOfType(25);
this.knownTypes.put(element.compoundName[element.compoundName.length - 1], element);
+ if (element.isAnnotationType() || element instanceof UnresolvedReferenceBinding) // unresolved types don't yet have the modifiers set
+ checkIfNullAnnotationType(element);
}
void clearMissingTagBit() {
@@ -230,6 +239,52 @@
return ProblemReasons.NoError;
}
+
+void checkIfNullAnnotationPackage() {
+ LookupEnvironment env = this.environment;
+ if (env.globalOptions.isAnnotationBasedNullAnalysisEnabled) {
+ if (isPackageOfQualifiedTypeName(this.compoundName, env.getNullableAnnotationName()))
+ env.nullableAnnotationPackage = this;
+ if (isPackageOfQualifiedTypeName(this.compoundName, env.getNonNullAnnotationName()))
+ env.nonnullAnnotationPackage = this;
+ if (isPackageOfQualifiedTypeName(this.compoundName, env.getNonNullByDefaultAnnotationName()))
+ env.nonnullByDefaultAnnotationPackage = this;
+ }
+}
+
+private boolean isPackageOfQualifiedTypeName(char[][] packageName, char[][] typeName) {
+ if (typeName == null || typeName.length -1 != packageName.length)
+ return false;
+ for (int i=0; i - Contributions for
- * bug 328281 - visibility leaks not detected when analyzing unused field in private class
- * bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 328281 - visibility leaks not detected when analyzing unused field in private class
+ * bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
@@ -54,6 +55,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 +785,7 @@
}
if ((this.tagBits & TagBits.AnnotationDeprecated) != 0)
this.modifiers |= ClassFileConstants.AccDeprecated;
+ evaluateNullAnnotations(this.tagBits);
}
return this.tagBits;
}
@@ -1088,6 +1093,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 +1597,102 @@
typeParameters[i].binding = null;
return null;
}
+ createArgumentBindings(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 createArgumentBindings(MethodBinding method) {
+ // ensure nullness defaults are initialized at all enclosing levels:
+ switch (this.nullnessDefaultInitialized) {
+ case 0:
+ getAnnotationTagBits(); // initialize
+ //$FALL-THROUGH$
+ case 1:
+ getPackage().isViewedAsDeprecated(); // initialize annotations
+ this.nullnessDefaultInitialized = 2;
+ }
+ AbstractMethodDeclaration methodDecl = method.sourceMethod();
+ if (methodDecl != null) {
+ if (method.parameters != Binding.NO_PARAMETERS)
+ methodDecl.createArgumentBindings();
+ 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)
+ this.nullnessDefaultAnnotation = this.scope.environment().getNullAnnotationResolved(this.nullnessDefaultAnnotation, this.scope);
+ 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(this.scope);
+ if (annotationBinding != null)
+ return annotationBinding;
+
+ // global
+ long defaultNullness = environment.globalOptions.defaultNonNullness;
+ if (defaultNullness != 0) {
+ // we have a default, so we need an annotation type to record this during compile and in the byte code
+ 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
+ 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..9ba6da0 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
@@ -132,6 +133,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 +153,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..7fff434 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
@@ -10,6 +10,7 @@
* Stephan Herrmann - Contributions for
* bug 349326 - [1.7] new warning for missing try-with-resources
* bug 359362 - FUP of bug 349326: Resource leak on non-Closeable resource
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
@@ -104,6 +105,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..ca6b9fa 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.parser;
@@ -546,11 +547,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 cbaf9aa..78f1521 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
@@ -8,10 +8,11 @@
* Contributors:
* IBM Corporation - initial API and implementation
* Benjamin Muskalla - Contribution for bug 239066
- * Stephan Herrmann - Contributions for
- * bug 236385 -
- * bug 338303 - Warning about Redundant assignment conflicts with definite assignment
- * bug 349326 - [1.7] new warning for missing try-with-resources
+ * Stephan Herrmann - Contributions for
+ * bug 236385 - [compiler] Warn for potential programming problem if an object is created but not used
+ * bug 338303 - Warning about Redundant assignment conflicts with definite assignment
+ * bug 349326 - [1.7] new warning for missing try-with-resources
+ * bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.problem;
@@ -84,6 +85,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 +289,7 @@
return CompilerOptions.NullReference;
case IProblem.PotentialNullLocalVariableReference:
+ case IProblem.PotentialNullMessageSendReference:
return CompilerOptions.PotentialNullReference;
case IProblem.RedundantLocalVariableNullAssignment:
@@ -295,7 +298,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 :
@@ -541,7 +561,14 @@
case CompilerOptions.ForbiddenReference :
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 +577,7 @@
switch (problemID) {
case IProblem.IsClassPathCorrect :
case IProblem.CorruptedSignature :
+ case IProblem.MissingNullAnnotationType :
return CategorizedProblem.CAT_BUILDPATH;
default :
@@ -8043,4 +8071,182 @@
trackVar.sourceStart,
trackVar.sourceEnd);
}
-}
\ No newline at end of file
+
+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; i - Contributions for
# bug 185682 - Increment/decrement operators mark local variables as read
# bug 349326 - [1.7] new warning for missing try-with-resources
+# bug 186342 - [compiler][null] Using annotations for null checking
###############################################################################
0 = {0}
1 = super cannot be used in java.lang.Object
@@ -655,6 +656,22 @@
889 = Resource ''{0}'' should be managed by try-with-resource
890 = Cannot switch on an enum value for source level below 1.5. Only convertible int values are permitted
+### NULL ANNOTATIONS
+910 = Type mismatch: required ''@{0} {1}'' but the provided value is null
+911 = Type mismatch: required ''@{0} {1}'' but the provided value can be null
+912 = Potential type mismatch: required ''@{0} {1}'' but nullness of the provided value is unknown
+913 = Buildpath problem: the type {0}, which is configured as a null annotation type, cannot be resolved
+914 = The return type is incompatible with the @{1} return from {0}
+915 = Illegal redefinition of parameter {0}, inherited method from {1} declares this parameter as @{2}
+916 = Illegal redefinition of parameter {0}, inherited method from {1} does not constrain this parameter
+917 = Missing non-null annotation: inherited method from {1} declares this parameter as @{2}
+918 = Missing nullable annotation: inherited method from {1} declares this parameter as @{2}
+919 = Potential null pointer access: The method {0} may return null
+920 = Redundant null check: The method {0} cannot return null
+921 = The method {0} from {1} cannot implement the corresponding method from {2} due to incompatible nullness constraints
+922 = The nullness annotation is redundant with a default that applies to this location
+923 = The nullness annotation @{0} is not applicable for the primitive type {1}
+
### ELABORATIONS
## Access restrictions
78592 = The type {1} is not accessible due to restriction on classpath entry {0}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java
index 0fdbd0a..00b8222 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for bug 363858 - [dom] early throwing of AbortCompilation causes NPE in CompilationUnitResolver
*******************************************************************************/
package org.eclipse.jdt.core.dom;
@@ -99,6 +100,7 @@
DefaultBindingResolver.BindingTables bindingTables;
boolean hasCompilationAborted;
+ CategorizedProblem abortProblem;
private IProgressMonitor monitor;
@@ -364,6 +366,7 @@
removeUnresolvedBindings(unit);
}
this.hasCompilationAborted = true;
+ this.abortProblem = abortException.problem;
}
public static void parse(ICompilationUnit[] compilationUnits, ASTRequestor astRequestor, int apiLevel, Map options, int flags, IProgressMonitor monitor) {
@@ -689,11 +692,16 @@
// the bindings could not be resolved due to missing types in name environment
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=86541
CompilationUnitDeclaration unitDeclaration = parse(sourceUnit, nodeSearcher, options, flags);
- final int problemCount = unit.compilationResult.problemCount;
- if (problemCount != 0) {
- unitDeclaration.compilationResult.problems = new CategorizedProblem[problemCount];
- System.arraycopy(unit.compilationResult.problems, 0, unitDeclaration.compilationResult.problems, 0, problemCount);
- unitDeclaration.compilationResult.problemCount = problemCount;
+ if (unit != null) {
+ final int problemCount = unit.compilationResult.problemCount;
+ if (problemCount != 0) {
+ unitDeclaration.compilationResult.problems = new CategorizedProblem[problemCount];
+ System.arraycopy(unit.compilationResult.problems, 0, unitDeclaration.compilationResult.problems, 0, problemCount);
+ unitDeclaration.compilationResult.problemCount = problemCount;
+ }
+ } else if (resolver.abortProblem != null) {
+ unitDeclaration.compilationResult.problemCount = 1;
+ unitDeclaration.compilationResult.problems = new CategorizedProblem[] { resolver.abortProblem };
}
return unitDeclaration;
}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java
index 750cc95..de363fa 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java
@@ -87,6 +87,12 @@
* COMPILER_PB_UNCLOSED_CLOSEABLE,
* COMPILER_PB_POTENTIALLY_UNCLOSED_CLOSEABLE
* COMPILER_PB_EXPLICITLY_CLOSED_AUTOCLOSEABLE
+ * COMPILER_ANNOTATION_NULL_ANALYSIS
+ * COMPILER_NULLABLE_ANNOTATION_NAME
+ * COMPILER_NONNULL_ANNOTATION_NAME
+ * COMPILER_PB_NULL_SPECIFICATION_VIOLATION
+ * COMPILER_PB_POTENTIAL_NULL_SPECIFICATION_VIOLATION
+ * COMPILER_PB_NULL_SPECIFICATION_INSUFFICIENT_INFO
*******************************************************************************/
package org.eclipse.jdt.core;
@@ -1407,6 +1413,213 @@
*/
public static final String COMPILER_PB_EXPLICITLY_CLOSED_AUTOCLOSEABLE = PLUGIN_ID + ".compiler.problem.explicitlyClosedAutoCloseable"; //$NON-NLS-1$
/**
+ * Compiler option ID: Annotation-based Null Analysis.
+ * This 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: Name of Annotation Type to specify a nullness default for unannotated types.
+ * This option defines a fully qualified Java type name that the compiler may use
+ * to perform special null analysis.
+ * If the annotation is applied without an argument all unannotated types in method signatures
+ * within the annotated element will be treated as if they were specified with the non-null annotation
+ * (see {@link #COMPILER_NONNULL_ANNOTATION_NAME}).
+ * If the annotation is applied without the constant false
as its argument
+ * all corresponding defaults at outer scopes will be canceled for the annotated element.
+ * This includes defaults specified using this annotation type or a default defined using
+ * the compiler option {@link #COMPILER_NONNULL_IS_DEFAULT}.
+ * Option id: "org.eclipse.jdt.core.compiler.annotation.nonnullbydefault"
+ * Possible values: any legal, fully qualified Java type name, must resolve to an annotation type.
+ * That annotation type should have exactly one boolean parameter.
+ * Default: "org.eclipse.jdt.annotation.NonNullByDefault"
+ * @since 3.8
+ * @category CompilerOptionID
+ */
+ public static final String COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME = PLUGIN_ID + ".compiler.annotation.nonnullbydefault"; //$NON-NLS-1$
+ /**
+ * Compiler option ID: Globally specify non-null as the assumed default for unannotated types.
+ * When enabled this option globally achieves the same effect
+ * as specifying {@link #COMPILER_NONNULL_ANNOTATION_NAME} does for individual elements.
+ * Option id: "org.eclipse.jdt.core.compiler.annotation.nonnullisdefault"
+ * Possible values: { "disabled", "enabled" }
.
+ * Default: "disabled"
+ * @since 3.8
+ * @category CompilerOptionID
+ */
+ public static final String COMPILER_NONNULL_IS_DEFAULT = PLUGIN_ID + ".compiler.annotation.nonnullisdefault"; //$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: Reporting Redundant Null Annotations.
+ * When enabled, the compiler will issue an error or a warning when a non-null annotation
+ * (see {@link #COMPILER_NONNULL_ANNOTATION_NAME})
+ * is applied although the same effect is already achieved by a default applicable at the
+ * current location. Such default may be effective by enabling the option
+ * {@link #COMPILER_NONNULL_IS_DEFAULT} or by using the annotation specified by the option
+ * {@link #COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME}.
+ *
+ * Option id: "org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation"
+ * Possible values: { "error", "warning", "ignore" }
+ * Default: "warning"
+ *
+ * @since 3.8
+ * @category CompilerOptionID
+ */
+ public static final String COMPILER_PB_REDUNDANT_NULL_ANNOTATION = PLUGIN_ID + ".compiler.problem.redundantNullAnnotation"; //$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
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java
index 8095a3d..b34bb5e 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ReconcileWorkingCopyOperation.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2008 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
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/AbstractImageBuilder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/AbstractImageBuilder.java
index 531b2b5..74bde9b 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/AbstractImageBuilder.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/AbstractImageBuilder.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