diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java index 53d8a6f..67abb24 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java @@ -52,7 +52,7 @@ // Static initializer to specify tests subset using TESTS_* static variables // All specified tests which do not belong to the class are skipped... static { -// TESTS_NAMES = new String[] { "test_assignment_expression_1" }; +// TESTS_NAMES = new String[] { "test_nonnull_parameter_015" }; // TESTS_NUMBERS = new int[] { 561 }; // TESTS_RANGE = new int[] { 1, 2049 }; } @@ -550,6 +550,158 @@ "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + "----------\n"); } +// non-null varargs (message send) +public void test_nonnull_parameter_015() { + 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" + + " void foo2(int i, @NonNull Object ... o) {\n" + + " if (o.length > 0 && o[0] != null)\n" + + " System.out.print(o[0].toString());\n" + + " }\n" + + " void bar() {\n" + + " foo((Object)null);\n" + + " foo(this, null);\n" + + " foo(null, this);\n" + + " foo2(2, (Object)null);\n" + + " foo2(2, this, null);\n" + + " foo2(2, null, this);\n" + + " foo2(2, (Object[])null);\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" + +// cannot yet look into arrays: +// "----------\n" + +// "2. ERROR in X.java (at line 8)\n" + +// " if (o.length > 0 && o[0] != null)\n" + +// " ^^^^\n" + +// "Redundant null check: The array variable o cannot contain null at this program location\n" + + "----------\n" + + "2. ERROR in X.java (at line 12)\n" + + " foo((Object)null);\n" + + " ^^^^^^^^^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "3. ERROR in X.java (at line 13)\n" + + " foo(this, null);\n" + + " ^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "4. ERROR in X.java (at line 14)\n" + + " foo(null, this);\n" + + " ^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "5. ERROR in X.java (at line 15)\n" + + " foo2(2, (Object)null);\n" + + " ^^^^^^^^^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "6. ERROR in X.java (at line 16)\n" + + " foo2(2, this, null);\n" + + " ^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "7. ERROR in X.java (at line 17)\n" + + " foo2(2, null, this);\n" + + " ^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "8. ERROR in X.java (at line 18)\n" + + " foo2(2, (Object[])null);\n" + + " ^^^^^^^^^^^^^^\n" + + "Type mismatch: required \'@NonNull Object[]\' but the provided value is null\n" + + "----------\n", + this.LIBS, + true /* shouldFlush*/); +} +// non-null varargs (allocation and explicit constructor calls) +public void test_nonnull_parameter_016() { + runNegativeTest( + new String[] { + "X.java", + "import org.eclipse.jdt.annotation.*;\n" + + "public class X {\n" + + " X(@NonNull Object ... o) {\n" + + " if (o != null)\n" + + " System.out.print(o.toString());\n" + + " }\n" + + " class Y extends X {\n" + + " Y(int i, @NonNull Object ... o) {\n" + + " super(i, (Object)null);\n" + + " }\n" + + " Y(char c, @NonNull Object ... o) {\n" + + " this(1, new Object(), null);\n" + + " }\n" + + " }\n" + + " void bar() {\n" + + " new X((Object)null);\n" + + " new X(this, null);\n" + + " X x = new X(null, this);\n" + + " x.new Y(2, (Object)null);\n" + + " this.new Y(2, null, this);\n" + + " this.new Y(2, (Object[])null);\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" + + "2. ERROR in X.java (at line 9)\n" + + " super(i, (Object)null);\n" + + " ^^^^^^^^^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "3. ERROR in X.java (at line 12)\n" + + " this(1, new Object(), null);\n" + + " ^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "4. ERROR in X.java (at line 16)\n" + + " new X((Object)null);\n" + + " ^^^^^^^^^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "5. ERROR in X.java (at line 17)\n" + + " new X(this, null);\n" + + " ^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "6. ERROR in X.java (at line 18)\n" + + " X x = new X(null, this);\n" + + " ^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "7. ERROR in X.java (at line 19)\n" + + " x.new Y(2, (Object)null);\n" + + " ^^^^^^^^^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "8. ERROR in X.java (at line 20)\n" + + " this.new Y(2, null, this);\n" + + " ^^^^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value is null\n" + + "----------\n" + + "9. ERROR in X.java (at line 21)\n" + + " this.new Y(2, (Object[])null);\n" + + " ^^^^^^^^^^^^^^\n" + + "Type mismatch: required \'@NonNull Object[]\' but the provided value is null\n" + + "----------\n", + this.LIBS, + true /* shouldFlush*/); +} // assigning potential null to a nonnull local variable public void test_nonnull_local_001() { runNegativeTest( 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 073ecc1..c03edc8 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 @@ -64,7 +64,26 @@ { // 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++) { + + // check if varargs need special treatment: + int regularParamsLen = methodBinding.parameters.length; + boolean passThrough = false; + if (methodBinding.isVarargs()) { + // this if-block essentially copied from generateArguments(..): + if (regularParamsLen == arguments.length) { + TypeBinding varArgsType = methodBinding.parameters[regularParamsLen-1]; + TypeBinding lastType = arguments[regularParamsLen-1].resolvedType; + if (lastType == TypeBinding.NULL + || (varArgsType.dimensions() == lastType.dimensions() + && lastType.isCompatibleWith(varArgsType))) + passThrough = true; // pass directly as-is + } + if (!passThrough) + regularParamsLen--; // last one is treated specially + } + + // main checking loop: + for (int i = 0; i < regularParamsLen; i++) { if (methodBinding.parameterNonNullness[i] == Boolean.TRUE) { TypeBinding expectedType = methodBinding.parameters[i]; Expression argument = arguments[i]; @@ -73,6 +92,19 @@ flowContext.recordNullityMismatch(currentScope, argument, nullStatus, expectedType); } } + + // if vararg is specified @NonNull: + if (!passThrough && methodBinding.isVarargs() && methodBinding.parameterNonNullness[regularParamsLen] == Boolean.TRUE) { + TypeBinding varArgsType = ((ArrayBinding)methodBinding.parameters[regularParamsLen]).elementsType(); // last type is array by definition + // continue loop until all arguments are handled: + int argumentsLen = arguments.length; + for (int i=regularParamsLen; i