| /******************************************************************************* |
| * Copyright (c) 2013, 2018 GK Software AG and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Stephan Herrmann - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.core.tests.compiler.regression; |
| |
| import java.util.Map; |
| |
| import junit.framework.Test; |
| |
| import org.eclipse.jdt.core.JavaCore; |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public class FlowAnalysisTest8 extends AbstractNullAnnotationTest { |
| |
| //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[] { "testReferenceExpression" }; |
| // TESTS_NUMBERS = new int[] { 561 }; |
| // TESTS_RANGE = new int[] { 1, 2049 }; |
| } |
| |
| public FlowAnalysisTest8(String name) { |
| super(name); |
| } |
| |
| public static Test suite() { |
| return buildMinimalComplianceTestSuite(testClass(), F_1_8); |
| } |
| |
| public static Class testClass() { |
| return FlowAnalysisTest8.class; |
| } |
| |
| // Lambda with elided args inherits null contract from the super method |
| public void testLambda_01() { |
| Map customOptions = getCompilerOptions(); |
| runNegativeTestWithLibs( |
| new String[] { |
| "ISAM.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public interface ISAM {\n" + |
| " @NonNull String toString(@NonNull String prefix, @Nullable Object o);\n" + |
| "}\n", |
| "X.java", |
| "public class X {\n" + |
| " void test() {\n" + |
| " ISAM printer = (p,o) -> p.concat(o.toString());\n" + |
| " }\n" + |
| "}\n" |
| }, |
| customOptions, |
| "----------\n" + |
| "1. WARNING in X.java (at line 3)\n" + |
| " ISAM printer = (p,o) -> p.concat(o.toString());\n" + |
| " ^^^^^^^^^^^^^^^^^^^^^^\n" + |
| "Null type safety (type annotations): The expression of type 'String' needs unchecked conversion to conform to \'@NonNull String\'\n" + |
| "----------\n" + |
| "2. ERROR in X.java (at line 3)\n" + |
| " ISAM printer = (p,o) -> p.concat(o.toString());\n" + |
| " ^\n" + |
| "Potential null pointer access: this expression has a '@Nullable' type\n" + |
| "----------\n"); |
| } |
| |
| // Lambda with declared args violates null contract of super |
| public void testLambda_02() { |
| Map customOptions = getCompilerOptions(); |
| runNegativeTestWithLibs( |
| new String[] { |
| "ISAM.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public interface ISAM {\n" + |
| " void process(@NonNull Object nn, @Nullable Object n, Object u);\n" + |
| "}\n", |
| "X.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public class X {\n" + |
| " void test() {\n" + |
| // try to override, illegal except for unchanged o1: |
| " ISAM printer = (@NonNull Object o1, @NonNull Object o2, @NonNull Object o3) -> System.out.println(2);\n" + |
| " }\n" + |
| "}\n" |
| }, |
| customOptions, |
| "----------\n" + |
| "1. ERROR in X.java (at line 4)\n" + |
| " ISAM printer = (@NonNull Object o1, @NonNull Object o2, @NonNull Object o3) -> System.out.println(2);\n" + |
| " ^^^^^^^^^^^^^^^^\n" + |
| "Illegal redefinition of parameter o2, inherited method from ISAM declares this parameter as @Nullable\n" + |
| "----------\n" + |
| "2. ERROR in X.java (at line 4)\n" + |
| " ISAM printer = (@NonNull Object o1, @NonNull Object o2, @NonNull Object o3) -> System.out.println(2);\n" + |
| " ^^^^^^^^^^^^^^^^\n" + |
| "Illegal redefinition of parameter o3, inherited method from ISAM does not constrain this parameter\n" + |
| "----------\n"); |
| } |
| |
| // Lambda with declared args inherits / modifies contract of super |
| public void testLambda_03() { |
| Map customOptions = getCompilerOptions(); |
| runNegativeTestWithLibs( |
| new String[] { |
| "ISAM.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public interface ISAM {\n" + |
| " void process(@NonNull Object nn, @Nullable Object n, Object u);\n" + |
| "}\n", |
| "X.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public class X {\n" + |
| " void test() {\n" + |
| // fill-in all from super: |
| " ISAM printer1 = (Object o1, Object o2, Object o3) \n" + |
| " -> System.out.println(o1.toString()+o2.toString()+o3.toString());\n" + |
| // legal overrides: (however, @NonNull -> @Nullable is probably nonsense) |
| " ISAM printer3 = (@Nullable Object o1, @Nullable Object o2, @Nullable Object o3) \n" + |
| " -> System.out.println(o1.toString()+o2.toString()+o3.toString());\n" + |
| " }\n" + |
| "}\n" |
| }, |
| customOptions, |
| "----------\n" + |
| "1. ERROR in X.java (at line 5)\n" + |
| " -> System.out.println(o1.toString()+o2.toString()+o3.toString());\n" + |
| " ^^\n" + |
| "Potential null pointer access: The variable o2 may be null at this location\n" + |
| "----------\n" + |
| "2. ERROR in X.java (at line 7)\n" + |
| " -> System.out.println(o1.toString()+o2.toString()+o3.toString());\n" + |
| " ^^\n" + |
| "Potential null pointer access: this expression has a '@Nullable' type\n" + |
| "----------\n" + |
| "3. ERROR in X.java (at line 7)\n" + |
| " -> System.out.println(o1.toString()+o2.toString()+o3.toString());\n" + |
| " ^^\n" + |
| "Potential null pointer access: this expression has a '@Nullable' type\n" + |
| "----------\n" + |
| "4. ERROR in X.java (at line 7)\n" + |
| " -> System.out.println(o1.toString()+o2.toString()+o3.toString());\n" + |
| " ^^\n" + |
| "Potential null pointer access: this expression has a '@Nullable' type\n" + |
| "----------\n"); |
| } |
| |
| // Lambda with declared args has illegal @NonNull an primitive argument |
| public void testLambda_04() { |
| Map customOptions = getCompilerOptions(); |
| runNegativeTestWithLibs( |
| new String[] { |
| "ISAM.java", |
| "public interface ISAM {\n" + |
| " void process(int i);\n" + |
| "}\n", |
| "X.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public class X {\n" + |
| " void test() {\n" + |
| " ISAM printer1 = (@NonNull int i) \n" + |
| " -> System.out.println(i);\n" + |
| " }\n" + |
| "}\n" |
| }, |
| customOptions, |
| "----------\n" + |
| "1. ERROR in X.java (at line 4)\n" + |
| " ISAM printer1 = (@NonNull int i) \n" + |
| " ^^^^^^^^\n" + |
| "The nullness annotation @NonNull is not applicable for the primitive type int\n" + |
| "----------\n"); |
| } |
| |
| // Lambda inherits null contract and has block with return statement |
| public void testLambda_05() { |
| Map customOptions = getCompilerOptions(); |
| runNegativeTestWithLibs( |
| new String[] { |
| "ISAM.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public interface ISAM {\n" + |
| " @NonNull String toString(Object o);\n" + |
| "}\n", |
| "X.java", |
| "public class X {\n" + |
| " void test() {\n" + |
| " ISAM printer = (o) -> {\n" + |
| " System.out.print(13);\n" + |
| " return null; // error\n" + |
| " };\n" + |
| " }\n" + |
| "}\n" |
| }, |
| customOptions, |
| "----------\n" + |
| "1. ERROR in X.java (at line 5)\n" + |
| " return null; // error\n" + |
| " ^^^^\n" + |
| "Null type mismatch: required \'@NonNull String\' but the provided value is null\n" + |
| "----------\n"); |
| } |
| // Lambda has no descriptor (overriding method from Object), don't bail out with NPE during analysis |
| public void testLambda_05a() { |
| Map customOptions = getCompilerOptions(); |
| runNegativeTest( |
| new String[] { |
| "ISAM.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public interface ISAM {\n" + |
| " @NonNull String toString();\n" + |
| "}\n", |
| "X.java", |
| "public class X {\n" + |
| " void test() {\n" + |
| " ISAM printer = () -> {\n" + |
| " System.out.print(13);\n" + |
| " return null;\n" + |
| " };\n" + |
| " }\n" + |
| "}\n" |
| }, |
| "----------\n" + |
| "1. ERROR in X.java (at line 3)\n" + |
| " ISAM printer = () -> {\n" + |
| " ^^^^^\n" + |
| "The target type of this expression must be a functional interface\n" + |
| "----------\n", |
| this.LIBS, |
| true /*flush*/, |
| customOptions); |
| } |
| // Test flows with ReferenceExpression regarding: |
| // - definite assignment |
| // - unused local |
| public void testReferenceExpression1() { |
| Map options = getCompilerOptions(); |
| options.put(JavaCore.COMPILER_PB_UNUSED_LOCAL, JavaCore.ERROR); |
| runNegativeTest( |
| new String[] { |
| "I.java", |
| "public interface I {\n" + |
| " public void bar();\n" + |
| "}\n", |
| "X.java", |
| "public class X {\n" + |
| " public void moo() {}\n" + |
| " public static void soo() {}\n" + |
| " void testAssignment() {\n" + |
| " X x;\n" + |
| " I i = x::moo; // x is unassigned\n" + |
| " i.bar();\n" + |
| " I i2 = X::soo;\n" + // OK |
| " }\n" + |
| " void testStatic() {\n" + |
| " X xs;\n" + |
| " I is = xs::soo;\n" + |
| " }\n" + |
| " void testUse() {\n" + |
| " X x1 = this, x2 = this; // x2 is not used, only x is\n" + |
| " I i = x1::moo;\n" + |
| " i.bar();\n" + |
| " }\n" + |
| "}\n" |
| }, |
| "----------\n" + |
| "1. ERROR in X.java (at line 6)\n" + |
| " I i = x::moo; // x is unassigned\n" + |
| " ^\n" + |
| "The local variable x may not have been initialized\n" + |
| "----------\n" + |
| "2. ERROR in X.java (at line 12)\n" + |
| " I is = xs::soo;\n" + |
| " ^^^^^^^\n" + |
| "The method soo() from the type X should be accessed in a static way \n" + |
| "----------\n" + |
| "3. ERROR in X.java (at line 15)\n" + |
| " X x1 = this, x2 = this; // x2 is not used, only x is\n" + |
| " ^^\n" + |
| "The value of the local variable x2 is not used\n" + |
| "----------\n", |
| null/*libs*/, true/*flush*/, options); |
| } |
| public void testReferenceExpression_null_1() { |
| Map options = getCompilerOptions(); |
| options.put(JavaCore.COMPILER_PB_NULL_REFERENCE, JavaCore.ERROR); |
| runNegativeTest( |
| false /*skipJavac */, |
| JavacTestOptions.Excuse.EclipseWarningConfiguredAsError, |
| new String[] { |
| "I.java", |
| "public interface I {\n" + |
| " public void foo();\n" + |
| "}\n", |
| "X.java", |
| "public class X {\n" + |
| " public void bar() {}\n" + |
| " void test() {\n" + |
| " X x = null;\n" + |
| " I i = x::bar;\n" + |
| " i.foo();\n" + |
| " }\n" + |
| "}\n" |
| }, |
| "----------\n" + |
| "1. ERROR in X.java (at line 5)\n" + |
| " I i = x::bar;\n" + |
| " ^\n" + |
| "Null pointer access: The variable x can only be null at this location\n" + |
| "----------\n", |
| null/*libs*/, true/*flush*/, options); |
| } |
| public void testReferenceExpression_nullAnnotation_1() { |
| runNegativeTestWithLibs( |
| new String[] { |
| "I.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public interface I {\n" + |
| " public @NonNull String foo(@Nullable Object s);\n" + |
| "}\n", |
| "X.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public class X {\n" + |
| " public @Nullable String bar(@NonNull Object s) { return s.toString(); }\n" + |
| " void test() {\n" + |
| " I i = this::bar;\n" + |
| " System.out.print(i.foo(null));\n" + |
| " }\n" + |
| "}\n" |
| }, |
| "----------\n" + |
| "1. ERROR in X.java (at line 5)\n" + |
| " I i = this::bar;\n" + |
| " ^^^^^^^^^\n" + |
| "Null type mismatch at parameter 1: required '@NonNull Object' but provided '@Nullable Object' via method descriptor I.foo(Object)\n" + |
| "----------\n" + |
| "2. ERROR in X.java (at line 5)\n" + |
| " I i = this::bar;\n" + |
| " ^^^^^^^^^\n" + |
| "Null type mismatch at method return type: Method descriptor I.foo(Object) promises '@NonNull String' but referenced method provides '@Nullable String'\n" + |
| "----------\n"); |
| } |
| public void testReferenceExpression_nullAnnotation_2() { |
| runWarningTestWithLibs( |
| true, /* skipJavac */ |
| new String[] { |
| "I.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public interface I {\n" + |
| " public @NonNull String foo(@Nullable Object s);\n" + |
| "}\n", |
| "X.java", |
| "public class X {\n" + |
| " public String bar(Object s) { return s.toString(); }\n" + |
| " void test() {\n" + |
| " I i = this::bar;\n" + |
| " System.out.print(i.foo(null));\n" + |
| " }\n" + |
| "}\n" |
| }, |
| getCompilerOptions(), |
| "----------\n" + |
| "1. WARNING in X.java (at line 4)\n" + |
| " I i = this::bar;\n" + |
| " ^^^^^^^^^\n" + |
| "Null type safety at method return type: Method descriptor I.foo(Object) promises \'@NonNull String\' but referenced method provides \'String\'\n" + |
| "----------\n"); |
| } |
| public void testReferenceExpression_nullAnnotation_3() { |
| runNegativeTest( |
| new String[] { |
| "I.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public interface I {\n" + |
| " public @NonNull String foo(Object s);\n" + |
| "}\n", |
| "X.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "public class X {\n" + |
| " public @NonNull String bar(@NonNull Object s) { return \"\"; }\n" + |
| " void test() {\n" + |
| " I i = this::bar;\n" + |
| " System.out.print(i.foo(null));\n" + |
| " }\n" + |
| " Zork zork;\n" + // make warning visible by forcing an error |
| "}\n" |
| }, |
| "----------\n" + |
| "1. WARNING in X.java (at line 5)\n" + |
| " I i = this::bar;\n" + |
| " ^^^^^^^^^\n" + |
| "Null type safety: parameter 1 provided via method descriptor I.foo(Object) needs unchecked conversion to conform to '@NonNull Object'\n" + |
| "----------\n" + |
| "2. ERROR in X.java (at line 8)\n" + |
| " Zork zork;\n" + |
| " ^^^^\n" + |
| "Zork cannot be resolved to a type\n" + |
| "----------\n", |
| this.LIBS, |
| true /*flush*/, |
| getCompilerOptions()); |
| } |
| public void testBug535308a() { |
| Runner runner = new Runner(); |
| runner.customOptions = getCompilerOptions(); |
| runner.customOptions.put(JavaCore.COMPILER_PB_UNUSED_LOCAL, JavaCore.ERROR); |
| runner.testFiles = |
| new String[] { |
| "X.java", |
| "public class X {\n" + |
| " public int someTest() {\n" + |
| " boolean unused = false;\n" + |
| " final boolean thisIsFalse = false;\n" + |
| " if (getSomeValue() == thisIsFalse) {\n" + |
| " return 0;\n" + |
| " }\n" + |
| " return 1;\n" + |
| " }\n" + |
| " private boolean getSomeValue() {\n" + |
| " return true;\n" + |
| " }\n" + |
| "}" |
| }; |
| runner.expectedCompilerLog = |
| "----------\n" + |
| "1. ERROR in X.java (at line 3)\n" + |
| " boolean unused = false;\n" + |
| " ^^^^^^\n" + |
| "The value of the local variable unused is not used\n" + |
| "----------\n"; |
| runner.classLibraries = |
| this.LIBS; |
| runner.javacTestOptions = JavacTestOptions.Excuse.EclipseWarningConfiguredAsError; |
| runner.runNegativeTest(); |
| } |
| public void testBug535308b() { |
| Runner runner = new Runner(); |
| runner.customOptions = getCompilerOptions(); |
| runner.customOptions.put(JavaCore.COMPILER_PB_UNUSED_LOCAL, JavaCore.ERROR); |
| runner.testFiles = |
| new String[] { |
| "X.java", |
| "public class X {\n" + |
| " public int someTest() {\n" + |
| " boolean unused = false;\n" + |
| " final boolean thisIsFalse = false;\n" + |
| " if (getSomeValue() != thisIsFalse) {\n" + |
| " return 0;\n" + |
| " }\n" + |
| " return 1;\n" + |
| " }\n" + |
| "\n" + |
| " private boolean getSomeValue() {\n" + |
| " return true;\n" + |
| " }\n" + |
| "}" |
| }; |
| runner.expectedCompilerLog = |
| "----------\n" + |
| "1. ERROR in X.java (at line 3)\n" + |
| " boolean unused = false;\n" + |
| " ^^^^^^\n" + |
| "The value of the local variable unused is not used\n" + |
| "----------\n"; |
| runner.classLibraries = |
| this.LIBS; |
| runner.javacTestOptions = JavacTestOptions.Excuse.EclipseWarningConfiguredAsError; |
| runner.runNegativeTest(); |
| } |
| public void testBug535308c() { |
| Runner runner = new Runner(); |
| runner.customOptions = getCompilerOptions(); |
| runner.customOptions.put(JavaCore.COMPILER_PB_UNUSED_LOCAL, JavaCore.ERROR); |
| runner.testFiles = |
| new String[] { |
| "X.java", |
| "public class X {\n" + |
| " public int someTest() {\n" + |
| " boolean unused = false;\n" + |
| " final boolean thisIsFalse = false;\n" + |
| " if (thisIsFalse != getSomeValue()) {\n" + |
| " return 0;\n" + |
| " }\n" + |
| " return 1;\n" + |
| " }\n" + |
| "\n" + |
| " private boolean getSomeValue() {\n" + |
| " return true;\n" + |
| " }\n" + |
| "}" |
| }; |
| runner.expectedCompilerLog = |
| "----------\n" + |
| "1. ERROR in X.java (at line 3)\n" + |
| " boolean unused = false;\n" + |
| " ^^^^^^\n" + |
| "The value of the local variable unused is not used\n" + |
| "----------\n"; |
| runner.classLibraries = |
| this.LIBS; |
| runner.javacTestOptions = JavacTestOptions.Excuse.EclipseWarningConfiguredAsError; |
| runner.runNegativeTest(); |
| } |
| public void testBug535308d() { |
| Runner runner = new Runner(); |
| runner.customOptions = getCompilerOptions(); |
| runner.customOptions.put(JavaCore.COMPILER_PB_UNUSED_LOCAL, JavaCore.ERROR); |
| runner.testFiles = |
| new String[] { |
| "X.java", |
| "public class X {\n" + |
| " public int someTest() {\n" + |
| " boolean unused = false;\n" + |
| " final boolean thisIsFalse = false;\n" + |
| " if (thisIsFalse == getSomeValue()) {\n" + |
| " return 0;\n" + |
| " }\n" + |
| " return 1;\n" + |
| " }\n" + |
| "\n" + |
| " private boolean getSomeValue() {\n" + |
| " return true;\n" + |
| " }\n" + |
| "}" |
| }; |
| runner.expectedCompilerLog = |
| "----------\n" + |
| "1. ERROR in X.java (at line 3)\n" + |
| " boolean unused = false;\n" + |
| " ^^^^^^\n" + |
| "The value of the local variable unused is not used\n" + |
| "----------\n"; |
| runner.classLibraries = |
| this.LIBS; |
| runner.javacTestOptions = JavacTestOptions.Excuse.EclipseWarningConfiguredAsError; |
| runner.runNegativeTest(); |
| } |
| public void testBug535308e() { |
| Runner runner = new Runner(); |
| runner.customOptions = getCompilerOptions(); |
| runner.customOptions.put(JavaCore.COMPILER_PB_UNUSED_LOCAL, JavaCore.ERROR); |
| runner.testFiles = |
| new String[] { |
| "X.java", |
| "public class X {\n" + |
| " public int someTest() {\n" + |
| " boolean used = false;\n" + |
| " final boolean thisIsFalse = false;\n" + |
| " if (used == getSomeValue()) {\n" + |
| " return 0;\n" + |
| " }\n" + |
| " return 1;\n" + |
| " }\n" + |
| "\n" + |
| " private boolean getSomeValue() {\n" + |
| " return true;\n" + |
| " }\n" + |
| "}" |
| }; |
| runner.expectedCompilerLog = |
| "----------\n" + |
| "1. ERROR in X.java (at line 4)\n" + |
| " final boolean thisIsFalse = false;\n" + |
| " ^^^^^^^^^^^\n" + |
| "The value of the local variable thisIsFalse is not used\n" + |
| "----------\n"; |
| runner.classLibraries = |
| this.LIBS; |
| runner.javacTestOptions = JavacTestOptions.Excuse.EclipseWarningConfiguredAsError; |
| runner.runNegativeTest(); |
| } |
| } |