diff options
author | Stephan Herrmann | 2016-10-17 21:38:07 +0000 |
---|---|---|
committer | Stephan Herrmann | 2017-03-05 23:44:58 +0000 |
commit | e7d6d8a91612da3f3cae0b22ddd55df43d4ee554 (patch) | |
tree | 75fa42a06ccdd59ca88e3b7bfe410adfbd8ef80b | |
parent | d111a821fb2982dae427d067dc0f6a0a36b9c804 (diff) | |
download | eclipse.jdt.core-e7d6d8a91612da3f3cae0b22ddd55df43d4ee554.tar.gz eclipse.jdt.core-e7d6d8a91612da3f3cae0b22ddd55df43d4ee554.tar.xz eclipse.jdt.core-e7d6d8a91612da3f3cae0b22ddd55df43d4ee554.zip |
Bug 410218 - Optional warning for arguments of "unexpected" types toI20170305-2000
Map#get(Object), Collection#remove(Object) et al.
Also-By: Till Brychcy <register.eclipse@brychcy.de>
Change-Id: I171086d745e1ba15f64cf66264ac35c1a07ef437
17 files changed, 1076 insertions, 16 deletions
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 f5e7ec224b..ea86675dfc 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 @@ -872,6 +872,9 @@ public void test012b(){ " unavoidableGenericProblems + ignore unavoidable type safety problems\n" + " due to raw APIs\n" + " unchecked + unchecked type operation\n" + + " unlikelyCollectionMethodArgumentType unlikely argument type for collection\n" + + " methods using Object\n" + + " unlikelyEqualsArgumentType unlikely argument type for equals()\n" + " unnecessaryElse unnecessary else clause\n" + " unqualifiedField unqualified reference to field\n" + " unused macro for unusedAllocation, unusedArgument,\n" + @@ -1066,6 +1069,9 @@ public void test012b(){ " <option key=\"org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock\" value=\"ignore\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.problem.unhandledWarningToken\" value=\"warning\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.problem.uninternedIdentityComparison\" value=\"disabled\"/>\n" + + " <option key=\"org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType\" value=\"warning\"/>\n" + + " <option key=\"org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict\" value=\"disabled\"/>\n" + + " <option key=\"org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType\" value=\"info\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.problem.unnecessaryElse\" value=\"ignore\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck\" value=\"ignore\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess\" value=\"ignore\"/>\n" + @@ -1971,7 +1977,6 @@ public void test032(){ " public void foo5(final Map<XX<?, ?>, XY> p1) {\n" + " p1.putAll(m1);\n" + " }\n" + - "\n" + " public void foo6(final Map<XX<?, ?>, XY> p1) {\n" + " m1.keySet().retainAll(p1.keySet());\n" + " m2.keySet().retainAll(p1.keySet());\n" + @@ -2152,7 +2157,6 @@ public void test032(){ " public void foo5(final Map<XX<?, ?>, XY> p1) {\n" + " p1.putAll(m1);\n" + " }\n" + - "\n" + " public void foo6(final Map<XX<?, ?>, XY> p1) {\n" + " m1.keySet().retainAll(p1.keySet());\n" + " m2.keySet().retainAll(p1.keySet());\n" + @@ -12120,6 +12124,49 @@ public void test329_nowarn_options() { true); } +// https://bugs.eclipse.org/bugs/show_bug.cgi?id=408815 +// -warn option - regression tests to check option unlikelyCollectionMethodArgumentType +public void test330_warn_options() { + this.runConformTest( + new String[] { + "p/X.java", + "package p;\n" + + "import java.util.Map;\n" + + "public class X {\n" + + " Integer foo(Map<String,Integer> map) {\n" + + " return map.get(3);\n" + + " }\n" + + "}\n", + }, + "\"" + OUTPUT_DIR + File.separator + "p" + File.separator + "X.java\"" + + " -sourcepath \"" + OUTPUT_DIR + "\"" + + " -1.5" + + " -warn:-unlikelyCollectionMethodArgumentType -proc:none -d \"" + OUTPUT_DIR + "\"", + "", + "", + true); +}//https://bugs.eclipse.org/bugs/show_bug.cgi?id=408815 +//-warn option - regression tests to check option unlikelyEqualsArgumentType +public void test331_warn_options() { + this.runConformTest( + new String[] { + "p/X.java", + "package p;\n" + + "public class X {\n" + + " boolean foo() {\n" + + " return \"three\".equals(3);\n" + + " }\n" + + "}\n", + }, + "\"" + OUTPUT_DIR + File.separator + "p" + File.separator + "X.java\"" + + " -sourcepath \"" + OUTPUT_DIR + "\"" + + " -1.5" + + " -info:-unlikelyEqualsArgumentType -proc:none -d \"" + OUTPUT_DIR + "\"", + "", + "", + true); +} + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=375409 public void testBug375409a() { this.runConformTest( 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 1d93cb4e52..fad307ec04 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 @@ -1046,6 +1046,8 @@ public void test011_problem_categories() { expectedProblemAttributes.put("UninitializedNonNullField", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); expectedProblemAttributes.put("UninitializedNonNullFieldHintMissingDefault", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); expectedProblemAttributes.put("UninternedIdentityComparison", new ProblemAttributes(CategorizedProblem.CAT_SYNTAX)); + expectedProblemAttributes.put("UnlikelyCollectionMethodArgumentType", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); + expectedProblemAttributes.put("UnlikelyEqualsArgumentType", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); expectedProblemAttributes.put("UnmatchedBracket", new ProblemAttributes(CategorizedProblem.CAT_SYNTAX)); expectedProblemAttributes.put("UnnecessaryArgumentCast", DEPRECATED); expectedProblemAttributes.put("UnnecessaryCast", new ProblemAttributes(CategorizedProblem.CAT_UNNECESSARY_CODE)); @@ -1887,6 +1889,8 @@ public void test012_compiler_problems_tuning() { expectedProblemAttributes.put("UninitializedNonNullField", SKIP); expectedProblemAttributes.put("UninitializedNonNullFieldHintMissingDefault", SKIP); expectedProblemAttributes.put("UninternedIdentityComparison", SKIP); + expectedProblemAttributes.put("UnlikelyCollectionMethodArgumentType", new ProblemAttributes(JavaCore.COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE)); + expectedProblemAttributes.put("UnlikelyEqualsArgumentType", new ProblemAttributes(JavaCore.COMPILER_PB_UNLIKELY_EQUALS_ARGUMENT_TYPE)); expectedProblemAttributes.put("UnmatchedBracket", SKIP); expectedProblemAttributes.put("UnnecessaryArgumentCast", SKIP); expectedProblemAttributes.put("UnnecessaryCast", new ProblemAttributes(JavaCore.COMPILER_PB_UNNECESSARY_TYPE_CHECK)); diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProgrammingProblemsTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProgrammingProblemsTest.java index b97333cc1a..46de7276d4 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProgrammingProblemsTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProgrammingProblemsTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2001, 2014 IBM Corporation and others. + * Copyright (c) 2001, 2017 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 @@ -10,6 +10,7 @@ * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contributions for * bug 185682 - Increment/decrement operators mark local variables as read * bug 328281 - visibility leaks not detected when analyzing unused field in private class + * Bug 410218 - Optional warning for arguments of "unexpected" types to Map#get(Object), Collection#remove(Object) et al. *******************************************************************************/ package org.eclipse.jdt.core.tests.compiler.regression; @@ -2703,4 +2704,521 @@ public void test0063() throws Exception { true/*shouldFlushOutputDirectory*/, customOptions); } +// Collection: contains & remove & get +public void testBug410218a() { + if (this.complianceLevel < ClassFileConstants.JDK1_5) + return; + runNegativeTest( + new String[] { + "X.java", + "import java.util.*;\n" + + "class X {\n" + + " void test() {\n" + + " Set<Short> set = new HashSet<Short>();\n" + + " short one = 1;\n" + + " set.add(one);\n" + + "\n" + + " if (set.contains(\"ONE\")) // bad\n" + + " set.remove(\"ONE\"); // bad\n" + + " if (set.contains(1)) // bad\n" + + " set.remove(1); // bad (tries to remove \"Integer 1\")\n" + + " System.out.println(set); // shows that the \"Short 1\" is still in!\n" + + "\n" + + " if (set.contains(one)) // ok\n" + + " set.remove(one); // ok\n" + + " if (set.contains(Short.valueOf(one))) // ok\n" + + " set.remove(Short.valueOf(one)); // ok\n" + + " System.out.println(set);\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. WARNING in X.java (at line 8)\n" + + " if (set.contains(\"ONE\")) // bad\n" + + " ^^^^^\n" + + "Unlikely argument type String for contains(Object) on a Collection<Short>\n" + + "----------\n" + + "2. WARNING in X.java (at line 9)\n" + + " set.remove(\"ONE\"); // bad\n" + + " ^^^^^\n" + + "Unlikely argument type String for remove(Object) on a Collection<Short>\n" + + "----------\n" + + "3. WARNING in X.java (at line 10)\n" + + " if (set.contains(1)) // bad\n" + + " ^\n" + + "Unlikely argument type int for contains(Object) on a Collection<Short>\n" + + "----------\n" + + "4. WARNING in X.java (at line 11)\n" + + " set.remove(1); // bad (tries to remove \"Integer 1\")\n" + + " ^\n" + + "Unlikely argument type int for remove(Object) on a Collection<Short>\n" + + "----------\n"); +} +// HashSet vs. TreeSet +public void testBug410218b() { + if (this.complianceLevel < ClassFileConstants.JDK1_5) + return; + runNegativeTest( + new String[] { + "X.java", + "import java.util.*;\n" + + "class X {\n" + + " <T> void test(Set<HashSet<T>> hss, TreeSet<T> ts, LinkedHashSet<T> lhs) {\n" + + " if (hss.contains(ts)) // bad\n" + + " hss.remove(ts); // bad\n" + + " if (hss.contains((Set<T>)ts)) // ok\n" + + " hss.remove((Set<T>)ts); // ok\n" + + " if (hss.contains(lhs)) // ok\n" + + " hss.remove(lhs); // ok\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. WARNING in X.java (at line 4)\n" + + " if (hss.contains(ts)) // bad\n" + + " ^^\n" + + "Unlikely argument type TreeSet<T> for contains(Object) on a Collection<HashSet<T>>\n" + + "----------\n" + + "2. WARNING in X.java (at line 5)\n" + + " hss.remove(ts); // bad\n" + + " ^^\n" + + "Unlikely argument type TreeSet<T> for remove(Object) on a Collection<HashSet<T>>\n" + + "----------\n"); +} +// HashSet vs. TreeSet or: strict +public void testBug410218b2() { + if (this.complianceLevel < ClassFileConstants.JDK1_5) + return; + Map customOptions = getCompilerOptions(); + customOptions.put(JavaCore.COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE_STRICT, JavaCore.ENABLED); + runNegativeTest( + new String[] { + "X.java", + "import java.util.*;\n" + + "class X {\n" + + " <T> void test(Set<HashSet<T>> hss, TreeSet<T> ts, LinkedHashSet<T> lhs) {\n" + + " if (hss.contains(ts)) // bad\n" + + " hss.remove(ts); // bad\n" + + " if (hss.contains((Set<T>)ts)) // bad (because of strict check)\n" + + " hss.remove((Set<T>)ts); // bad (because of strict check)\n" + + " if (hss.contains(lhs)) // ok\n" + + " hss.remove(lhs); // ok\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. WARNING in X.java (at line 4)\n" + + " if (hss.contains(ts)) // bad\n" + + " ^^\n" + + "Unlikely argument type TreeSet<T> for contains(Object) on a Collection<HashSet<T>>\n" + + "----------\n" + + "2. WARNING in X.java (at line 5)\n" + + " hss.remove(ts); // bad\n" + + " ^^\n" + + "Unlikely argument type TreeSet<T> for remove(Object) on a Collection<HashSet<T>>\n" + + "----------\n" + + "3. WARNING in X.java (at line 6)\n" + + " if (hss.contains((Set<T>)ts)) // bad (because of strict check)\n" + + " ^^^^^^^^^^\n" + + "Unlikely argument type Set<T> for contains(Object) on a Collection<HashSet<T>>\n" + + "----------\n" + + "4. WARNING in X.java (at line 7)\n" + + " hss.remove((Set<T>)ts); // bad (because of strict check)\n" + + " ^^^^^^^^^^\n" + + "Unlikely argument type Set<T> for remove(Object) on a Collection<HashSet<T>>\n" + + "----------\n", + null/*classLibraries*/, + true/*shouldFlushOutputDirectory*/, + customOptions); +} +// Map: contains* & remove & get +public void testBug410218c() { + if (this.complianceLevel < ClassFileConstants.JDK1_5) + return; + runNegativeTest( + new String[] { + "X.java", + "import java.util.*;\n" + + "class X {\n" + + " Number test(Map<? extends Number, Number> m, boolean f) {\n" + + " if (m.containsKey(\"ONE\")) // bad\n" + + " m.remove(\"ONE\"); // bad\n" + + " if (m.containsValue(\"ONE\")) // bad\n" + + " m.remove(\"ONE\"); // bad\n" + + " short one = 1;\n" + + " if (m.containsKey(one)) // almost ok\n" + + " m.remove(one); // almost ok\n" + + " if (m.containsValue(Short.valueOf(one))) // ok\n" + + " m.remove(Short.valueOf(one)); // almost ok\n" + + " if (f)\n" + + " return m.get(\"ONE\"); // bad\n" + + " return m.get(one);\n // almost ok\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. WARNING in X.java (at line 4)\n" + + " if (m.containsKey(\"ONE\")) // bad\n" + + " ^^^^^\n" + + "Unlikely argument type String for containsKey(Object) on a Map<capture#1-of ? extends Number,Number>\n" + + "----------\n" + + "2. WARNING in X.java (at line 5)\n" + + " m.remove(\"ONE\"); // bad\n" + + " ^^^^^\n" + + "Unlikely argument type String for remove(Object) on a Map<capture#2-of ? extends Number,Number>\n" + + "----------\n" + + "3. WARNING in X.java (at line 6)\n" + + " if (m.containsValue(\"ONE\")) // bad\n" + + " ^^^^^\n" + + "Unlikely argument type String for containsValue(Object) on a Map<capture#3-of ? extends Number,Number>\n" + + "----------\n" + + "4. WARNING in X.java (at line 7)\n" + + " m.remove(\"ONE\"); // bad\n" + + " ^^^^^\n" + + "Unlikely argument type String for remove(Object) on a Map<capture#4-of ? extends Number,Number>\n" + + "----------\n" + + "5. WARNING in X.java (at line 14)\n" + + " return m.get(\"ONE\"); // bad\n" + + " ^^^^^\n" + + "Unlikely argument type String for get(Object) on a Map<capture#9-of ? extends Number,Number>\n" + + "----------\n"); +} +// Collection: {contains,remove,retain}All, non-generic sub type of Collection, configured to be ERROR +public void testBug410218d() { + if (this.complianceLevel < ClassFileConstants.JDK1_5) + return; + Map customOptions = getCompilerOptions(); + customOptions.put(JavaCore.COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE, JavaCore.ERROR); + runNegativeTest( + new String[] { + "X.java", + "import java.util.*;\n" + + "interface NumberCollection extends Collection<Number> {}\n" + + "class X {\n" + + " void test(NumberCollection numbers, List<Integer> ints, Set<String> stringSet) {\n" + + " if (numbers.containsAll(ints)) // ok\n" + + " numbers.removeAll(ints); // ok\n" + + " else\n" + + " numbers.retainAll(ints); // ok\n" + + "\n" + + " numbers.removeAll(stringSet); // bad\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. ERROR in X.java (at line 10)\n" + + " numbers.removeAll(stringSet); // bad\n" + + " ^^^^^^^^^\n" + + "Unlikely argument type Set<String> for removeAll(Collection<?>) on a Collection<Number>\n" + + "----------\n", + null/*classLibraries*/, + true/*shouldFlushOutputDirectory*/, + customOptions); +} +// List.indexOf: w/ and w/o @SuppressWarnings +public void testBug410218e() { + if (this.complianceLevel < ClassFileConstants.JDK1_5) + return; + Map customOptions = getCompilerOptions(); + customOptions.put(JavaCore.COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE, JavaCore.WARNING); + runNegativeTest( + new String[] { + "X.java", + "import java.util.*;\n" + + "class X {\n" + + " int test1(List<Integer> ints, Object o) {\n" + + " return ints.indexOf(\"ONE\"); // bad\n" + + " }\n" + + " @SuppressWarnings(\"unlikely-arg-type\")\n" + + " int test2(List<Integer> ints, boolean f, Object o) {\n" + + " if (f)\n" + + " return ints.indexOf(\"ONE\"); // bad but suppressed\n" + + " return ints.indexOf(o); // supertype\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. WARNING in X.java (at line 4)\n" + + " return ints.indexOf(\"ONE\"); // bad\n" + + " ^^^^^\n" + + "Unlikely argument type String for indexOf(Object) on a List<Integer>\n" + + "----------\n", + null/*classLibraries*/, + true/*shouldFlushOutputDirectory*/, + customOptions); +} + +// Method references, equals, wildcards +public void testBug410218f() { + if (this.complianceLevel < ClassFileConstants.JDK1_8) + return; + Map customOptions = getCompilerOptions(); + customOptions.put(JavaCore.COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE, JavaCore.WARNING); + customOptions.put(JavaCore.COMPILER_PB_UNLIKELY_EQUALS_ARGUMENT_TYPE, JavaCore.INFO); + runNegativeTest( + new String[] { + "test/TestUnlikely.java", + "package test;\n" + + "\n" + + "import java.util.Collection;\n" + + "import java.util.Iterator;\n" + + "import java.util.List;\n" + + "import java.util.Map;\n" + + "import java.util.Objects;\n" + + "import java.util.Set;\n" + + "import java.util.function.BiPredicate;\n" + + "import java.util.function.Predicate;\n" + + "\n" + + "public class TestUnlikely {\n" + + " interface Interface {\n" + + " }\n" + + "\n" + + " interface OtherInterface {\n" + + " }\n" + + "\n" + + " static class NonFinal implements Interface {\n" + + " }\n" + + "\n" + + " static class Sub extends NonFinal implements OtherInterface {\n" + + " }\n" + + "\n" + + " static final class Final implements Interface {\n" + + " }\n" + + "\n" + + " void f1(List<Interface> c, Interface i, OtherInterface o, Final f, NonFinal nf, Sub s) {\n" + + " c.remove(i);\n" + + " c.remove(o); // warning: unrelated interface\n" + + " c.remove(f);\n" + + " c.remove(nf);\n" + + " c.remove(s);\n" + + " }\n" + + "\n" + + " void f2(List<OtherInterface> c, Interface i, OtherInterface o, Final f, NonFinal nf, Sub s) {\n" + + " c.remove(i); // warning: unrelated interface\n" + + " c.remove(o);\n" + + " c.remove(f); // warning: impossible\n" + + " c.remove(nf); // warning: castable, but not supertype\n" + + " c.remove(s);\n" + + " }\n" + + "\n" + + " void f3(List<Final> c, Interface i, OtherInterface o, Final f, NonFinal nf, Sub s) {\n" + + " c.remove(i); // supertype\n" + + " c.remove(o); // warning: impossible\n" + + " c.remove(f);\n" + + " c.remove(nf); // warning: impossible\n" + + " c.remove(s); // warning: impossible\n" + + " }\n" + + "\n" + + " void f4(List<NonFinal> c, Interface i, OtherInterface o, Final f, NonFinal nf, Sub s) {\n" + + " c.remove(i); // supertype\n" + + " c.remove(o); // warning: unrelated interface\n" + + " c.remove(f); // warning: impossible\n" + + " c.remove(nf);\n" + + " c.remove(s);\n" + + " }\n" + + "\n" + + " void f5(List<Sub> c, Interface i, OtherInterface o, Final f, NonFinal nf, Sub s) {\n" + + " c.remove(i); // supertype\n" + + " c.remove(o); // supertype\n" + + " c.remove(f); // warning: impossible\n" + + " c.remove(nf); // supertype\n" + + " c.remove(s);\n" + + " }\n" + + "\n" + + " <K, V> void map(Map<K, V> map, K key, V value) {\n" + + " map.containsKey(key);\n" + + " map.containsKey(value); // warning\n" + + " map.containsValue(key); // warning\n" + + " map.containsValue(value);\n" + + " }\n" + + "\n" + + " boolean wildcards(Collection<?> c, Iterable<?> s) {\n" + + " for (Iterator<?> iterator = s.iterator(); iterator.hasNext();) {\n" + + " if (c.contains(iterator.next())) {\n" + + " return true;\n" + + " }\n" + + " }\n" + + " return false;\n" + + " }\n" + + "\n" + + " <T, U extends T> boolean relatedTypeVariables(Collection<T> c, Iterable<U> s) {\n" + + " for (Iterator<?> iterator = s.iterator(); iterator.hasNext();) {\n" + + " if (c.contains(iterator.next())) {\n" + + " return true;\n" + + " }\n" + + " }\n" + + " return false;\n" + + " }\n" + + "\n" + + " <T, U> boolean unrelatedTypeVariables(Collection<T> c, Iterable<U> s) {\n" + + " for (Iterator<U> iterator = s.iterator(); iterator.hasNext();) {\n" + + " if (c.contains(iterator.next())) { // warning\n" + + " return true;\n" + + " }\n" + + " }\n" + + " return false;\n" + + " }\n" + + "\n" + + " void all(List<NonFinal> c, Collection<Sub> s, Set<Final> other) {\n" + + " c.removeAll(s);\n" + + " s.removeAll(c);\n" + + " c.removeAll(other); // warning\n" + + " }\n" + + "\n" + + " void methodRef(Set<Interface> c, Interface i, OtherInterface o, Final f, NonFinal nf, Sub s) {\n" + + " Predicate<Interface> p1 = c::contains;\n" + + " BiPredicate<Collection<Interface>, Interface> bp1 = Collection<Interface>::contains;\n" + + " Predicate<OtherInterface> p2 = c::contains; // warning\n" + + " BiPredicate<Collection<Interface>, OtherInterface> bp2 = Collection<Interface>::contains; // warning\n" + + " p1.test(i);\n" + + " bp1.test(c, i);\n" + + " p2.test(o);\n" + + " bp2.test(c, o);\n" + + " }\n" + + "\n" + + " void equals(String s, Integer i, Number n) {\n" + + " s.equals(i); // info\n" + + " i.equals(s); // info\n" + + " i.equals(n);\n" + + " n.equals(i);\n" + + "\n" + + " Predicate<String> p1 = i::equals; // info\n" + + " p1.test(s);\n" + + "\n" + + " BiPredicate<String, Integer> bp2 = Object::equals; // info\n" + + " bp2.test(s, i);\n" + + "\n" + + " Objects.equals(s, i); // info\n" + + " Objects.equals(i, s); // info\n" + + " Objects.equals(n, i);\n" + + " Objects.equals(i, n);\n" + + "\n" + + " BiPredicate<String, Integer> bp3 = Objects::equals; // info\n" + + " bp3.test(s, i);\n" + + " }\n" + + "\n" + + "}\n" + + "", + }, + "----------\n" + + "1. WARNING in test\\TestUnlikely.java (at line 30)\n" + + " c.remove(o); // warning: unrelated interface\n" + + " ^\n" + + "Unlikely argument type TestUnlikely.OtherInterface for remove(Object) on a Collection<TestUnlikely.Interface>\n" + + "----------\n" + + "2. WARNING in test\\TestUnlikely.java (at line 37)\n" + + " c.remove(i); // warning: unrelated interface\n" + + " ^\n" + + "Unlikely argument type TestUnlikely.Interface for remove(Object) on a Collection<TestUnlikely.OtherInterface>\n" + + "----------\n" + + "3. WARNING in test\\TestUnlikely.java (at line 39)\n" + + " c.remove(f); // warning: impossible\n" + + " ^\n" + + "Unlikely argument type TestUnlikely.Final for remove(Object) on a Collection<TestUnlikely.OtherInterface>\n" + + "----------\n" + + "4. WARNING in test\\TestUnlikely.java (at line 40)\n" + + " c.remove(nf); // warning: castable, but not supertype\n" + + " ^^\n" + + "Unlikely argument type TestUnlikely.NonFinal for remove(Object) on a Collection<TestUnlikely.OtherInterface>\n" + + "----------\n" + + "5. WARNING in test\\TestUnlikely.java (at line 46)\n" + + " c.remove(o); // warning: impossible\n" + + " ^\n" + + "Unlikely argument type TestUnlikely.OtherInterface for remove(Object) on a Collection<TestUnlikely.Final>\n" + + "----------\n" + + "6. WARNING in test\\TestUnlikely.java (at line 48)\n" + + " c.remove(nf); // warning: impossible\n" + + " ^^\n" + + "Unlikely argument type TestUnlikely.NonFinal for remove(Object) on a Collection<TestUnlikely.Final>\n" + + "----------\n" + + "7. WARNING in test\\TestUnlikely.java (at line 49)\n" + + " c.remove(s); // warning: impossible\n" + + " ^\n" + + "Unlikely argument type TestUnlikely.Sub for remove(Object) on a Collection<TestUnlikely.Final>\n" + + "----------\n" + + "8. WARNING in test\\TestUnlikely.java (at line 54)\n" + + " c.remove(o); // warning: unrelated interface\n" + + " ^\n" + + "Unlikely argument type TestUnlikely.OtherInterface for remove(Object) on a Collection<TestUnlikely.NonFinal>\n" + + "----------\n" + + "9. WARNING in test\\TestUnlikely.java (at line 55)\n" + + " c.remove(f); // warning: impossible\n" + + " ^\n" + + "Unlikely argument type TestUnlikely.Final for remove(Object) on a Collection<TestUnlikely.NonFinal>\n" + + "----------\n" + + "10. WARNING in test\\TestUnlikely.java (at line 63)\n" + + " c.remove(f); // warning: impossible\n" + + " ^\n" + + "Unlikely argument type TestUnlikely.Final for remove(Object) on a Collection<TestUnlikely.Sub>\n" + + "----------\n" + + "11. WARNING in test\\TestUnlikely.java (at line 70)\n" + + " map.containsKey(value); // warning\n" + + " ^^^^^\n" + + "Unlikely argument type V for containsKey(Object) on a Map<K,V>\n" + + "----------\n" + + "12. WARNING in test\\TestUnlikely.java (at line 71)\n" + + " map.containsValue(key); // warning\n" + + " ^^^\n" + + "Unlikely argument type K for containsValue(Object) on a Map<K,V>\n" + + "----------\n" + + "13. WARNING in test\\TestUnlikely.java (at line 95)\n" + + " if (c.contains(iterator.next())) { // warning\n" + + " ^^^^^^^^^^^^^^^\n" + + "Unlikely argument type U for contains(Object) on a Collection<T>\n" + + "----------\n" + + "14. WARNING in test\\TestUnlikely.java (at line 105)\n" + + " c.removeAll(other); // warning\n" + + " ^^^^^\n" + + "Unlikely argument type Set<TestUnlikely.Final> for removeAll(Collection<?>) on a Collection<TestUnlikely.NonFinal>\n" + + "----------\n" + + "15. WARNING in test\\TestUnlikely.java (at line 111)\n" + + " Predicate<OtherInterface> p2 = c::contains; // warning\n" + + " ^^^^^^^^^^^\n" + + "Unlikely argument type TestUnlikely.OtherInterface for contains(Object) on a Collection<TestUnlikely.Interface>\n" + + "----------\n" + + "16. WARNING in test\\TestUnlikely.java (at line 112)\n" + + " BiPredicate<Collection<Interface>, OtherInterface> bp2 = Collection<Interface>::contains; // warning\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Unlikely argument type TestUnlikely.OtherInterface for contains(Object) on a Collection<TestUnlikely.Interface>\n" + + "----------\n" + + "17. INFO in test\\TestUnlikely.java (at line 120)\n" + + " s.equals(i); // info\n" + + " ^\n" + + "Unlikely argument type for equals(): Integer seems to be unrelated to String\n" + + "----------\n" + + "18. INFO in test\\TestUnlikely.java (at line 121)\n" + + " i.equals(s); // info\n" + + " ^\n" + + "Unlikely argument type for equals(): String seems to be unrelated to Integer\n" + + "----------\n" + + "19. INFO in test\\TestUnlikely.java (at line 125)\n" + + " Predicate<String> p1 = i::equals; // info\n" + + " ^^^^^^^^^\n" + + "Unlikely argument type for equals(): String seems to be unrelated to Integer\n" + + "----------\n" + + "20. INFO in test\\TestUnlikely.java (at line 128)\n" + + " BiPredicate<String, Integer> bp2 = Object::equals; // info\n" + + " ^^^^^^^^^^^^^^\n" + + "Unlikely argument type for equals(): Integer seems to be unrelated to String\n" + + "----------\n" + + "21. INFO in test\\TestUnlikely.java (at line 131)\n" + + " Objects.equals(s, i); // info\n" + + " ^\n" + + "Unlikely argument type for equals(): Integer seems to be unrelated to String\n" + + "----------\n" + + "22. INFO in test\\TestUnlikely.java (at line 132)\n" + + " Objects.equals(i, s); // info\n" + + " ^\n" + + "Unlikely argument type for equals(): String seems to be unrelated to Integer\n" + + "----------\n" + + "23. INFO in test\\TestUnlikely.java (at line 136)\n" + + " BiPredicate<String, Integer> bp3 = Objects::equals; // info\n" + + " ^^^^^^^^^^^^^^^\n" + + "Unlikely argument type for equals(): Integer seems to be unrelated to String\n" + + "----------\n" + , + null/*classLibraries*/, + true/*shouldFlushOutputDirectory*/, + customOptions); +} }
\ No newline at end of file diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java index f95d7c8c78..0f4bb64958 100644 --- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java +++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java @@ -4012,6 +4012,12 @@ private void handleErrorOrWarningToken(String token, boolean isEnabling, int sev } else if (token.equals("unchecked") || token.equals("unsafe")) {//$NON-NLS-1$ //$NON-NLS-2$ setSeverity(CompilerOptions.OPTION_ReportUncheckedTypeOperation, severity, isEnabling); return; + } else if (token.equals("unlikelyCollectionMethodArgumentType")) { //$NON-NLS-1$ + setSeverity(CompilerOptions.OPTION_ReportUnlikelyCollectionMethodArgumentType, severity, isEnabling); + return; + } else if (token.equals("unlikelyEqualsArgumentType")) { //$NON-NLS-1$ + setSeverity(CompilerOptions.OPTION_ReportUnlikelyEqualsArgumentType, severity, isEnabling); + return; } else if (token.equals("unnecessaryElse")) {//$NON-NLS-1$ setSeverity(CompilerOptions.OPTION_ReportUnnecessaryElse, severity, isEnabling); return; diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties index c56494df66..5f2f54f9b7 100644 --- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties +++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties @@ -412,6 +412,9 @@ misc.usage.warn = {1} {2}\n\ \ unavoidableGenericProblems + ignore unavoidable type safety problems\n\ \ due to raw APIs\n\ \ unchecked + unchecked type operation\n\ +\ unlikelyCollectionMethodArgumentType unlikely argument type for collection\n\ +\ methods using 'Object'\n\ +\ unlikelyEqualsArgumentType unlikely argument type for equals()\n\ \ unnecessaryElse unnecessary else clause\n\ \ unqualifiedField unqualified reference to field\n\ \ unused macro for unusedAllocation, unusedArgument,\n\ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java index c0ac572225..12f6fe05e1 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java @@ -196,6 +196,8 @@ * IllegalParameterNullityRedefinition * ContradictoryNullAnnotationsInferredFunctionType * IllegalReturnNullityRedefinitionFreeTypeVariable + * UnlikelyCollectionMethodArgumentType + * UnlikelyEqualsArgumentType * Jesper S Moller - added the following constants * TargetTypeNotAFunctionalInterface * OuterLocalMustBeEffectivelyFinal @@ -1889,4 +1891,9 @@ void setSourceStart(int sourceStart); int LambdaShapeComputationError = 1101; /** @since 3.13 */ int ProblemNotAnalysed = 1102; + + /** @since 3.13 */ + int UnlikelyCollectionMethodArgumentType = 1200; + /** @since 3.13 */ + int UnlikelyEqualsArgumentType = 1201; } 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 44c2ad0fad..a1be3fddef 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2016 IBM Corporation and others. + * Copyright (c) 2000, 2017 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 @@ -53,6 +53,7 @@ * Bug 407414 - [compiler][null] Incorrect warning on a primitive type being null * Bug 472618 - [compiler][null] assertNotNull vs. Assert.assertNotNull * Bug 470958 - [1.8] Unable to convert lambda + * Bug 410218 - Optional warning for arguments of "unexpected" types to Map#get(Object), Collection#remove(Object) et al. * Jesper S Moller - Contributions for * Bug 378674 - "The method can be declared as static" is wrong * Andy Clement (GoPivotal, Inc) aclement@gopivotal.com - Contributions for @@ -75,6 +76,7 @@ import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.impl.Constant; +import org.eclipse.jdt.internal.compiler.impl.IrritantSet; import org.eclipse.jdt.internal.compiler.impl.ReferenceContext; import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; @@ -164,6 +166,27 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl } } } + if (compilerOptions.isAnyEnabled(IrritantSet.UNLIKELY_ARGUMENT_TYPE) && this.binding.isValidBinding() + && this.arguments != null) { + if (this.arguments.length == 1 && !this.binding.isStatic()) { + UnlikelyArgumentCheck argumentChecks = UnlikelyArgumentCheck.determineCheckForNonStaticSingleArgumentMethod( + this.argumentTypes[0], currentScope, this.selector, this.actualReceiverType, this.binding.parameters); + + if (argumentChecks != null && argumentChecks.isDangerous(currentScope)) { + currentScope.problemReporter().unlikelyArgumentType(this.arguments[0], this.binding, + this.argumentTypes[0], argumentChecks.typeToReport, argumentChecks.dangerousMethod); + } + } else if (this.arguments.length == 2 && this.binding.isStatic()) { + UnlikelyArgumentCheck argumentChecks = UnlikelyArgumentCheck.determineCheckForStaticTwoArgumentMethod( + this.argumentTypes[1], currentScope, this.selector, this.argumentTypes[0], + this.binding.parameters, this.actualReceiverType); + + if (argumentChecks != null && argumentChecks.isDangerous(currentScope)) { + currentScope.problemReporter().unlikelyArgumentType(this.arguments[1], this.binding, + this.argumentTypes[1], argumentChecks.typeToReport, argumentChecks.dangerousMethod); + } + } + } if (nonStatic) { int timeToLive = ((this.bits & ASTNode.InsideExpressionStatement) != 0) ? 3 : 2; @@ -301,6 +324,7 @@ private int detectAssertionUtility(int argumentIdx) { } return 0; } + private FlowInfo analyseBooleanAssertion(BlockScope currentScope, Expression argument, FlowContext flowContext, FlowInfo flowInfo, boolean wasInsideAssert, boolean passOnTrue) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java index fcd74c4cd5..b572490cda 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java @@ -61,6 +61,7 @@ import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.impl.Constant; +import org.eclipse.jdt.internal.compiler.impl.IrritantSet; import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; @@ -432,6 +433,33 @@ public class ReferenceExpression extends FunctionalExpression implements IPolyEx } } } + if (currentScope.compilerOptions().isAnyEnabled(IrritantSet.UNLIKELY_ARGUMENT_TYPE) && this.binding.isValidBinding() + && this.binding != null && this.binding.parameters != null) { + if (this.binding.parameters.length == 1 + && this.descriptor.parameters.length == (this.receiverPrecedesParameters ? 2 : 1) + && !this.binding.isStatic()) { + final TypeBinding argumentType = this.descriptor.parameters[this.receiverPrecedesParameters ? 1 : 0]; + final TypeBinding actualReceiverType = this.receiverPrecedesParameters ? this.descriptor.parameters[0] : this.binding.declaringClass; + UnlikelyArgumentCheck argumentCheck = UnlikelyArgumentCheck + .determineCheckForNonStaticSingleArgumentMethod(argumentType, currentScope, this.selector, + actualReceiverType, this.binding.parameters); + if (argumentCheck != null && argumentCheck.isDangerous(currentScope)) { + currentScope.problemReporter().unlikelyArgumentType(this, this.binding, argumentType, + argumentCheck.typeToReport, argumentCheck.dangerousMethod); + } + } else if (this.binding.parameters.length == 2 && this.descriptor.parameters.length == 2 && this.binding.isStatic()) { + final TypeBinding argumentType1 = this.descriptor.parameters[0]; + final TypeBinding argumentType2 = this.descriptor.parameters[1]; + UnlikelyArgumentCheck argumentCheck = UnlikelyArgumentCheck + .determineCheckForStaticTwoArgumentMethod(argumentType2, currentScope, this.selector, + argumentType1, this.binding.parameters, this.receiverType); + if (argumentCheck != null && argumentCheck.isDangerous(currentScope)) { + currentScope.problemReporter().unlikelyArgumentType(this, this.binding, argumentType2, + argumentCheck.typeToReport, argumentCheck.dangerousMethod); + } + } + } + manageSyntheticAccessIfNecessary(currentScope, flowInfo); return flowInfo; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnlikelyArgumentCheck.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnlikelyArgumentCheck.java new file mode 100644 index 0000000000..de80f94473 --- /dev/null +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnlikelyArgumentCheck.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2015, 2017 GK Software AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Stephan Herrmann - initial API and implementation + *******************************************************************************/ + +package org.eclipse.jdt.internal.compiler.ast; + +import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; +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.TypeConstants.DangerousMethod; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; + +/* @NonNullByDefault */ +public class UnlikelyArgumentCheck { + public final DangerousMethod dangerousMethod; + public final TypeBinding typeToCheck; + public final TypeBinding expectedType; + public final TypeBinding typeToReport; + + private UnlikelyArgumentCheck(DangerousMethod dangerousMethod, TypeBinding typeToCheck, TypeBinding expectedType, + TypeBinding typeToReport) { + this.dangerousMethod = dangerousMethod; + this.typeToCheck = typeToCheck; + this.expectedType = expectedType; + this.typeToReport = typeToReport; + } + + /** + * Check if the invocation is likely a bug. + * @param currentScope + * @return false, if the typeToCheck does not seem to related to the expectedType + */ + public boolean isDangerous(BlockScope currentScope) { + TypeBinding typeToCheck2 = this.typeToCheck; + // take autoboxing into account + if (typeToCheck2.isBaseType()) { + typeToCheck2 = currentScope.boxing(typeToCheck2); + } + TypeBinding expectedType2 = this.expectedType; + if (expectedType2.isBaseType()) { // can happen for the first parameter of java.util.Object.equals + expectedType2 = currentScope.boxing(expectedType2); + } + if(this.dangerousMethod != DangerousMethod.Equals && currentScope.compilerOptions().reportUnlikelyCollectionMethodArgumentTypeStrict) { + return !typeToCheck2.isCompatibleWith(expectedType2, currentScope); + } + // unless both types are true type variables (not captures), take the erasure of both. + if (typeToCheck2.isCapture() || !typeToCheck2.isTypeVariable() || expectedType2.isCapture() + || !expectedType2.isTypeVariable()) { + typeToCheck2 = typeToCheck2.erasure(); + expectedType2 = expectedType2.erasure(); + } + return !typeToCheck2.isCompatibleWith(expectedType2, currentScope) + && !expectedType2.isCompatibleWith(typeToCheck2, currentScope); + } + + /** + * When targeting a well-known dangerous method, returns an UnlikelyArgumentCheck object that contains the types to + * check against each other and to report + */ + public static /* @Nullable */ UnlikelyArgumentCheck determineCheckForNonStaticSingleArgumentMethod( + TypeBinding argumentType, Scope scope, char[] selector, TypeBinding actualReceiverType, + TypeBinding[] parameters) { + // detecting only methods with a single argument, typed either as Object or as Collection: + if (parameters.length != 1) + return null; + int paramTypeId = parameters[0].original().id; + if (paramTypeId != TypeIds.T_JavaLangObject && paramTypeId != TypeIds.T_JavaUtilCollection) + return null; + + // check selectors before typeBits as to avoid unnecessary super-traversals for the receiver type + DangerousMethod suspect = DangerousMethod.detectSelector(selector); + if (suspect == null) + return null; + + if (actualReceiverType.hasTypeBit(TypeIds.BitMap)) { + if (paramTypeId == TypeIds.T_JavaLangObject) { + switch (suspect) { + case ContainsKey: + case Get: + case Remove: + // map operations taking a key + ReferenceBinding mapType = actualReceiverType + .findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilMap, false); + if (mapType != null && mapType.isParameterizedType()) + return new UnlikelyArgumentCheck(suspect, argumentType, + ((ParameterizedTypeBinding) mapType).typeArguments()[0], mapType); + break; + case ContainsValue: + // map operation taking a value + mapType = actualReceiverType.findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilMap, false); + if (mapType != null && mapType.isParameterizedType()) + return new UnlikelyArgumentCheck(suspect, argumentType, + ((ParameterizedTypeBinding) mapType).typeArguments()[1], mapType); + break; + default: // no other suspects are detected in java.util.Map + } + } + } + if (actualReceiverType.hasTypeBit(TypeIds.BitCollection)) { + if (paramTypeId == TypeIds.T_JavaLangObject) { + switch (suspect) { + case Remove: + case Contains: + // collection operations taking a single element + ReferenceBinding collectionType = actualReceiverType + .findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilCollection, false); + if (collectionType != null && collectionType.isParameterizedType()) + return new UnlikelyArgumentCheck(suspect, argumentType, + ((ParameterizedTypeBinding) collectionType).typeArguments()[0], collectionType); + break; + default: // no other suspects with Object-parameter are detected in java.util.Collection + } + } else if (paramTypeId == TypeIds.T_JavaUtilCollection) { + switch (suspect) { + case RemoveAll: + case ContainsAll: + case RetainAll: + // collection operations taking another collection + ReferenceBinding collectionType = actualReceiverType + .findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilCollection, false); + ReferenceBinding argumentCollectionType = argumentType + .findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilCollection, false); + if (collectionType != null && argumentCollectionType != null + && argumentCollectionType.isParameterizedTypeWithActualArguments()) { + return new UnlikelyArgumentCheck(suspect, + ((ParameterizedTypeBinding) argumentCollectionType).typeArguments()[0], + ((ParameterizedTypeBinding) collectionType).typeArguments()[0], collectionType); + } + break; + default: // no other suspects with Collection-parameter are detected in java.util.Collection + } + } + if (actualReceiverType.hasTypeBit(TypeIds.BitList)) { + if (paramTypeId == TypeIds.T_JavaLangObject) { + switch (suspect) { + case IndexOf: + case LastIndexOf: + // list operations taking a single element + ReferenceBinding listType = actualReceiverType + .findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilList, false); + if (listType != null && listType.isParameterizedType()) + return new UnlikelyArgumentCheck(suspect, argumentType, + ((ParameterizedTypeBinding) listType).typeArguments()[0], listType); + break; + default: // no other suspects are detected in java.util.List + } + } + } + } + if (paramTypeId == TypeIds.T_JavaLangObject && suspect == DangerousMethod.Equals) { + return new UnlikelyArgumentCheck(suspect, argumentType, actualReceiverType, actualReceiverType); + } + return null; // not replacing + } + public static /* @Nullable */ UnlikelyArgumentCheck determineCheckForStaticTwoArgumentMethod( + TypeBinding secondParameter, Scope scope, char[] selector, TypeBinding firstParameter, + TypeBinding[] parameters, TypeBinding actualReceiverType) { + // detecting only methods with two arguments, both typed as Object: + if (parameters.length != 2) + return null; + int paramTypeId1 = parameters[0].original().id; + int paramTypeId2 = parameters[1].original().id; + + if (paramTypeId1 != TypeIds.T_JavaLangObject || paramTypeId2 != TypeIds.T_JavaLangObject) + return null; + + // check selectors before typeBits as to avoid unnecessary super-traversals for the receiver type + DangerousMethod suspect = DangerousMethod.detectSelector(selector); + if (suspect == null) + return null; + + if (actualReceiverType.id == TypeIds.T_JavaUtilObjects && suspect == DangerousMethod.Equals) { + return new UnlikelyArgumentCheck(suspect, secondParameter, firstParameter, firstParameter); + } + return null; + } +}
\ No newline at end of file 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 22c2272429..a0cc71d403 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2016 IBM Corporation and others. + * Copyright (c) 2000, 2017 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 @@ -20,6 +20,7 @@ * bug 381443 - [compiler][null] Allow parameter widening from @NonNull to unannotated * bug 383368 - [compiler][null] syntactic null analysis for field references * Bug 435805 - [1.8][compiler][null] Java 8 compiler does not recognize declaration style null annotations + * Bug 410218 - Optional warning for arguments of "unexpected" types to Map#get(Object), Collection#remove(Object) et al. * Jesper Steen Moller - Contributions for * bug 404146 - [1.7][compiler] nested try-catch-finally-blocks leads to unrunnable Java byte code * bug 407297 - [1.8][compiler] Control generation of parameter names by option @@ -161,9 +162,11 @@ public class CompilerOptions { public static final String OPTION_ReportMethodCanBeStatic = "org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic"; //$NON-NLS-1$ public static final String OPTION_ReportMethodCanBePotentiallyStatic = "org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic"; //$NON-NLS-1$ public static final String OPTION_ReportRedundantSpecificationOfTypeArguments = "org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments"; //$NON-NLS-1$ + 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_ReportNullAnnotationInferenceConflict = "org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict"; //$NON-NLS-1$ public static final String OPTION_ReportNullUncheckedConversion = "org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion"; //$NON-NLS-1$ @@ -187,6 +190,10 @@ public class CompilerOptions { public static final String OPTION_PessimisticNullAnalysisForFreeTypeVariables = "org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables"; //$NON-NLS-1$ public static final String OPTION_ReportNonNullTypeVariableFromLegacyInvocation = "org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation"; //$NON-NLS-1$ + public static final String OPTION_ReportUnlikelyCollectionMethodArgumentType = "org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType"; //$NON-NLS-1$ + public static final String OPTION_ReportUnlikelyCollectionMethodArgumentTypeStrict = "org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict"; //$NON-NLS-1$ + public static final String OPTION_ReportUnlikelyEqualsArgumentType = "org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType"; //$NON-NLS-1$ + /** * Possible values for configurable options */ @@ -309,6 +316,8 @@ public class CompilerOptions { public static final int UnusedExceptionParameter = IrritantSet.GROUP2 | ASTNode.Bit19; public static final int PessimisticNullAnalysisForFreeTypeVariables = IrritantSet.GROUP2 | ASTNode.Bit20; public static final int NonNullTypeVariableFromLegacyInvocation = IrritantSet.GROUP2 | ASTNode.Bit21; + public static final int UnlikelyCollectionMethodArgumentType = IrritantSet.GROUP2 | ASTNode.Bit22; + public static final int UnlikelyEqualsArgumentType = IrritantSet.GROUP2 | ASTNode.Bit23; // Severity level for handlers /** @@ -328,12 +337,12 @@ public class CompilerOptions { protected IrritantSet infoThreshold; /** - * Default settings are to be defined in {@lnk CompilerOptions#resetDefaults()} + * Default settings are to be defined in {@link CompilerOptions#resetDefaults()} */ /** Classfile debug information, may contain source file name, line numbers, local variable tables, etc... */ public int produceDebugAttributes; - /** Classfile method patameters information as per JEP 118... */ + /** Classfile method parameters information as per JEP 118... */ public boolean produceMethodParameters; /** Indicates whether generic signature should be generated for lambda expressions */ public boolean generateGenericSignatureForLambdaExpressions; @@ -468,6 +477,9 @@ public class CompilerOptions { /** Should missing enum cases be reported even if a default case exists in the same switch? */ public boolean reportMissingEnumCaseDespiteDefault; + /** When checking for unlikely argument types of of Map.get() et al, perform strict analysis against the expected type */ + public boolean reportUnlikelyCollectionMethodArgumentTypeStrict; + /** Should the compiler tolerate illegal ambiguous varargs invocation in compliance < 1.7 * to be bug compatible with javac? (bug 383780) */ public static boolean tolerateIllegalAmbiguousVarargsInvocation; @@ -493,7 +505,6 @@ public class CompilerOptions { /** Not directly configurable, derived from other options by LookupEnvironment.usesNullTypeAnnotations() */ public Boolean useNullTypeAnnotations = null; - // keep in sync with warningTokenToIrritant and warningTokenFromIrritant public final static String[] warningTokens = { @@ -519,6 +530,7 @@ public class CompilerOptions { "synthetic-access", //$NON-NLS-1$ "sync-override", //$NON-NLS-1$ "unchecked", //$NON-NLS-1$ + "unlikely-arg-type", //$NON-NLS-1$ "unqualified-field-access", //$NON-NLS-1$ "unused", //$NON-NLS-1$ }; @@ -714,6 +726,10 @@ public class CompilerOptions { return OPTION_PessimisticNullAnalysisForFreeTypeVariables; case NonNullTypeVariableFromLegacyInvocation: return OPTION_ReportNonNullTypeVariableFromLegacyInvocation; + case UnlikelyCollectionMethodArgumentType: + return OPTION_ReportUnlikelyCollectionMethodArgumentType; + case UnlikelyEqualsArgumentType: + return OPTION_ReportUnlikelyEqualsArgumentType; } return null; } @@ -905,7 +921,9 @@ public class CompilerOptions { OPTION_SyntacticNullAnalysisForFields, OPTION_ReportUnusedTypeParameter, OPTION_InheritNullAnnotations, - OPTION_ReportNonnullParameterAnnotationDropped + OPTION_ReportNonnullParameterAnnotationDropped, + OPTION_ReportUnlikelyCollectionMethodArgumentType, + OPTION_ReportUnlikelyEqualsArgumentType, }; return result; } @@ -997,6 +1015,9 @@ public class CompilerOptions { return "javadoc"; //$NON-NLS-1$ case MissingSynchronizedModifierInInheritedMethod: return "sync-override"; //$NON-NLS-1$ + case UnlikelyEqualsArgumentType: + case UnlikelyCollectionMethodArgumentType: + return "unlikely-arg-type"; //$NON-NLS-1$ } return null; } @@ -1077,6 +1098,8 @@ public class CompilerOptions { return IrritantSet.UNCHECKED; if ("unqualified-field-access".equals(warningToken)) //$NON-NLS-1$ return IrritantSet.UNQUALIFIED_FIELD_ACCESS; + if ("unlikely-arg-type".equals(warningToken)) //$NON-NLS-1$ + return IrritantSet.UNLIKELY_ARGUMENT_TYPE; break; } return null; @@ -1222,6 +1245,9 @@ public class CompilerOptions { optionsMap.put(OPTION_ReportUninternedIdentityComparison, this.complainOnUninternedIdentityComparison ? ENABLED : DISABLED); optionsMap.put(OPTION_PessimisticNullAnalysisForFreeTypeVariables, getSeverityString(PessimisticNullAnalysisForFreeTypeVariables)); optionsMap.put(OPTION_ReportNonNullTypeVariableFromLegacyInvocation, getSeverityString(NonNullTypeVariableFromLegacyInvocation)); + optionsMap.put(OPTION_ReportUnlikelyCollectionMethodArgumentType, getSeverityString(UnlikelyCollectionMethodArgumentType)); + optionsMap.put(OPTION_ReportUnlikelyCollectionMethodArgumentTypeStrict, this.reportUnlikelyCollectionMethodArgumentTypeStrict ? ENABLED : DISABLED); + optionsMap.put(OPTION_ReportUnlikelyEqualsArgumentType, getSeverityString(UnlikelyEqualsArgumentType)); return optionsMap; } @@ -1726,6 +1752,11 @@ public class CompilerOptions { 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_ReportUnusedTypeParameter)) != null) updateSeverity(UnusedTypeParameter, optionValue); + if ((optionValue = optionsMap.get(OPTION_ReportUnlikelyCollectionMethodArgumentType)) != null) updateSeverity(UnlikelyCollectionMethodArgumentType, optionValue); + if ((optionValue = optionsMap.get(OPTION_ReportUnlikelyCollectionMethodArgumentTypeStrict)) != null) { + this.reportUnlikelyCollectionMethodArgumentTypeStrict = ENABLED.equals(optionValue); + } + if ((optionValue = optionsMap.get(OPTION_ReportUnlikelyEqualsArgumentType)) != null) updateSeverity(UnlikelyEqualsArgumentType, optionValue); if (getSeverity(UnclosedCloseable) == ProblemSeverities.Ignore && getSeverity(PotentiallyUnclosedCloseable) == ProblemSeverities.Ignore && getSeverity(ExplicitlyClosedAutoCloseable) == ProblemSeverities.Ignore) { @@ -2053,6 +2084,9 @@ public class CompilerOptions { buf.append("\n\t- Unused Type Parameter: ").append(getSeverityString(UnusedTypeParameter)); //$NON-NLS-1$ buf.append("\n\t- pessimistic null analysis for free type variables: ").append(getSeverityString(PessimisticNullAnalysisForFreeTypeVariables)); //$NON-NLS-1$ buf.append("\n\t- report unsafe nonnull return from legacy method: ").append(getSeverityString(NonNullTypeVariableFromLegacyInvocation)); //$NON-NLS-1$ + buf.append("\n\t- unlikely argument type for collection methods: ").append(getSeverityString(UnlikelyCollectionMethodArgumentType)); //$NON-NLS-1$ + buf.append("\n\t- unlikely argument type for collection methods, strict check against expected type: ").append(this.reportUnlikelyCollectionMethodArgumentTypeStrict ? ENABLED : DISABLED); //$NON-NLS-1$ + buf.append("\n\t- unlikely argument types for equals(): ").append(getSeverityString(UnlikelyEqualsArgumentType)); //$NON-NLS-1$ return buf.toString(); } 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 280a502ed0..8ba156b86a 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2016 IBM Corporation and others. + * Copyright (c) 2000, 2017 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 @@ -15,6 +15,7 @@ * bug 374605 - Unreasonable warning for enum-based switch statements * bug 381443 - [compiler][null] Allow parameter widening from @NonNull to unannotated * Bug 441208 - [1.8][null]SuppressWarnings("null") does not suppress / marked Unnecessary + * Bug 410218 - Optional warning for arguments of "unexpected" types to Map#get(Object), Collection#remove(Object) et al. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.impl; @@ -67,12 +68,18 @@ public class IrritantSet { public static final IrritantSet UNCHECKED = new IrritantSet(CompilerOptions.UncheckedTypeOperation); public static final IrritantSet UNQUALIFIED_FIELD_ACCESS = new IrritantSet(CompilerOptions.UnqualifiedFieldAccess); public static final IrritantSet RESOURCE = new IrritantSet(CompilerOptions.UnclosedCloseable); + public static final IrritantSet UNLIKELY_ARGUMENT_TYPE = new IrritantSet(CompilerOptions.UnlikelyCollectionMethodArgumentType); public static final IrritantSet JAVADOC = new IrritantSet(CompilerOptions.InvalidJavadoc); public static final IrritantSet COMPILER_DEFAULT_ERRORS = new IrritantSet(0); // no optional error by default public static final IrritantSet COMPILER_DEFAULT_WARNINGS = new IrritantSet(0); // see static initializer below public static final IrritantSet COMPILER_DEFAULT_INFOS = new IrritantSet(0); // As of now, no default values static { + COMPILER_DEFAULT_INFOS + // group-2 infos enabled by default + .set( + CompilerOptions.UnlikelyEqualsArgumentType); + COMPILER_DEFAULT_WARNINGS // group-0 warnings enabled by default .set( @@ -117,7 +124,8 @@ public class IrritantSet { |CompilerOptions.RedundantNullAnnotation |CompilerOptions.NonnullParameterAnnotationDropped |CompilerOptions.PessimisticNullAnalysisForFreeTypeVariables - |CompilerOptions.NonNullTypeVariableFromLegacyInvocation); + |CompilerOptions.NonNullTypeVariableFromLegacyInvocation + |CompilerOptions.UnlikelyCollectionMethodArgumentType); // default errors IF AnnotationBasedNullAnalysis is enabled: COMPILER_DEFAULT_ERRORS.set( CompilerOptions.NullSpecViolation @@ -169,6 +177,9 @@ public class IrritantSet { JAVADOC .set(CompilerOptions.MissingJavadocComments) .set(CompilerOptions.MissingJavadocTags); + + UNLIKELY_ARGUMENT_TYPE + .set(CompilerOptions.UnlikelyEqualsArgumentType); } // Internal state diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java index 96ba4afe66..a128e232af 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java @@ -35,6 +35,7 @@ * Bug 452788 - [1.8][compiler] Type not correctly inferred in lambda expression * Bug 446442 - [1.8] merge null annotations from super methods * Bug 456532 - [1.8][null] ReferenceBinding.appendNullAnnotation() includes phantom annotations in error messages + * Bug 410218 - Optional warning for arguments of "unexpected" types to Map#get(Object), Collection#remove(Object) et al. * Jesper S Moller - Contributions for * bug 382701 - [1.8][compiler] Implement semantic analysis of Lambda expressions & Reference expression * bug 412153 - [1.8][compiler] Check validity of annotations which may be repeatable @@ -503,13 +504,27 @@ public void computeId() { if (CharOperation.equals(packageName, TypeConstants.UTIL)) { switch (typeName[0]) { case 'C' : - if (CharOperation.equals(typeName, TypeConstants.JAVA_UTIL_COLLECTION[2])) + if (CharOperation.equals(typeName, TypeConstants.JAVA_UTIL_COLLECTION[2])) { this.id = TypeIds.T_JavaUtilCollection; + this.typeBits |= TypeIds.BitCollection; + } return; case 'I' : if (CharOperation.equals(typeName, TypeConstants.JAVA_UTIL_ITERATOR[2])) this.id = TypeIds.T_JavaUtilIterator; return; + case 'L' : + if (CharOperation.equals(typeName, TypeConstants.JAVA_UTIL_LIST[2])) { + this.id = TypeIds.T_JavaUtilList; + this.typeBits |= TypeIds.BitList; + } + return; + case 'M' : + if (CharOperation.equals(typeName, TypeConstants.JAVA_UTIL_MAP[2])) { + this.id = TypeIds.T_JavaUtilMap; + this.typeBits |= TypeIds.BitMap; + } + return; case 'O' : if (CharOperation.equals(typeName, TypeConstants.JAVA_UTIL_OBJECTS[2])) this.id = TypeIds.T_JavaUtilObjects; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java index d37a9d0160..5f5360a6df 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2016 IBM Corporation and others. + * Copyright (c) 2000, 2017 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 @@ -17,6 +17,7 @@ * Bug 427199 - [1.8][resource] avoid resource leak warnings on Streams that have no resource * Bug 425183 - [1.8][inference] make CaptureBinding18 safe * Bug 429958 - [1.8][null] evaluate new DefaultLocation attribute of @NonNullByDefault + * Bug 410218 - Optional warning for arguments of "unexpected" types to Map#get(Object), Collection#remove(Object) et al. * Jesper S Moller - Contributions for * Bug 405066 - [1.8][compiler][codegen] Implement code generation infrastructure for JSR335 * Bug 412153 - [1.8][compiler] Check validity of annotations which may be repeatable @@ -27,6 +28,8 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; +import org.eclipse.jdt.core.compiler.CharOperation; + // TODO should rename into TypeNames (once extracted last non name constants) public interface TypeConstants { @@ -160,6 +163,7 @@ public interface TypeConstants { char[][] JAVA_UTIL_COLLECTION = {JAVA, UTIL, "Collection".toCharArray()}; //$NON-NLS-1$ char[][] JAVA_UTIL_ITERATOR = {JAVA, UTIL, "Iterator".toCharArray()}; //$NON-NLS-1$ char[][] JAVA_UTIL_OBJECTS = {JAVA, UTIL, "Objects".toCharArray()}; //$NON-NLS-1$ + char[][] JAVA_UTIL_LIST = {JAVA, UTIL, "List".toCharArray()}; //$NON-NLS-1$ char[][] JAVA_LANG_DEPRECATED = {JAVA, LANG, "Deprecated".toCharArray()}; //$NON-NLS-1$ char[][] JAVA_LANG_ANNOTATION_DOCUMENTED = {JAVA, LANG, ANNOTATION, "Documented".toCharArray()}; //$NON-NLS-1$ char[][] JAVA_LANG_ANNOTATION_INHERITED = {JAVA, LANG, ANNOTATION, "Inherited".toCharArray()}; //$NON-NLS-1$ @@ -333,6 +337,75 @@ public interface TypeConstants { char[][] COM_GOOGLE_INJECT_INJECT = new char[][] {COM, GOOGLE, INJECT_PACKAGE, INJECT_TYPE }; // detail for the above: char[] OPTIONAL = "optional".toCharArray(); //$NON-NLS-1$ + + // well-known methods with "dangerous" signatures: + char[][] JAVA_UTIL_MAP = new char[][] { JAVA, UTIL, "Map".toCharArray() }; //$NON-NLS-1$ + char[] GET = "get".toCharArray(); //$NON-NLS-1$ + char[] REMOVE = "remove".toCharArray(); //$NON-NLS-1$ + char[] REMOVE_ALL = "removeAll".toCharArray(); //$NON-NLS-1$ + char[] CONTAINS_ALL = "containsAll".toCharArray(); //$NON-NLS-1$ + char[] RETAIN_ALL = "retainAll".toCharArray(); //$NON-NLS-1$ + char[] CONTAINS_KEY = "containsKey".toCharArray(); //$NON-NLS-1$ + char[] CONTAINS_VALUE = "containsValue".toCharArray(); //$NON-NLS-1$ + // for Collection.contains: + char[] CONTAINS = "contains".toCharArray(); //$NON-NLS-1$ + // for List.*indexOf: + char[] INDEX_OF = "indexOf".toCharArray(); //$NON-NLS-1$ + char[] LAST_INDEX_OF = "lastIndexOf".toCharArray(); //$NON-NLS-1$ + enum DangerousMethod { + // Collection: + Contains, Remove, RemoveAll, ContainsAll, RetainAll, + // Map: + Get, ContainsKey, ContainsValue, + // List: + IndexOf, LastIndexOf, + // Object: + Equals; + + public static DangerousMethod detectSelector(char[] selector) { + switch (selector[0]) { + case 'r': + if (CharOperation.prefixEquals(TypeConstants.REMOVE, selector)) { + if (CharOperation.equals(selector, TypeConstants.REMOVE)) + return DangerousMethod.Remove; + else if (CharOperation.equals(selector, TypeConstants.REMOVE_ALL)) + return DangerousMethod.RemoveAll; + } else if (CharOperation.equals(selector, TypeConstants.RETAIN_ALL)) { + return DangerousMethod.RetainAll; + } + break; + case 'c': + if (CharOperation.prefixEquals(TypeConstants.CONTAINS, selector)) { + if (CharOperation.equals(selector, TypeConstants.CONTAINS)) + return DangerousMethod.Contains; + else if (CharOperation.equals(selector, TypeConstants.CONTAINS_ALL)) + return DangerousMethod.ContainsAll; + else if (CharOperation.equals(selector, TypeConstants.CONTAINS_KEY)) + return DangerousMethod.ContainsKey; + else if (CharOperation.equals(selector, TypeConstants.CONTAINS_VALUE)) + return DangerousMethod.ContainsValue; + } + break; + case 'g': + if (CharOperation.equals(selector, TypeConstants.GET)) + return DangerousMethod.Get; + break; + case 'i': + if (CharOperation.equals(selector, TypeConstants.INDEX_OF)) + return DangerousMethod.IndexOf; + break; + case 'l': + if (CharOperation.equals(selector, TypeConstants.LAST_INDEX_OF)) + return DangerousMethod.LastIndexOf; + break; + case 'e': + if (CharOperation.equals(selector, TypeConstants.EQUALS)) + return DangerousMethod.Equals; + break; + } + return null; + } + } // Spring @Autowired annotation char [] AUTOWIRED = "Autowired".toCharArray(); //$NON-NLS-1$ 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 7749b66f35..425672f525 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 @@ -14,6 +14,7 @@ * bug 358903 - Filter practically unimportant resource leak warnings * bug 400421 - [compiler] Null analysis for fields does not take @com.google.inject.Inject into account * bug 382069 - [null] Make the null analysis consider JUnit's assertNotNull similarly to assertions + * Bug 410218 - Optional warning for arguments of "unexpected" types to Map#get(Object), Collection#remove(Object) et al. * Jesper S Moller <jesper@selskabet.org> - Contributions for * Bug 412153 - [1.8][compiler] Check validity of annotations which may be repeatable * Ulrich Grave <ulrich.grave@gmx.de> - Contributions for @@ -132,6 +133,11 @@ public interface TypeIds { // Java 8 - JEP 120 final int T_JavaLangAnnotationRepeatable = 90; + + // classes with methods with "dangerous" signatures: + final int T_JavaUtilMap = 91; + final int T_JavaUtilList = 92; + // If you add new type id, make sure to bump up T_LastWellKnownTypeId if there is a cross over. final int T_LastWellKnownTypeId = 128; @@ -253,8 +259,17 @@ public interface TypeIds { final int BitNonNullByDefaultAnnotation = 128; final int BitAnyNullAnnotation = BitNonNullAnnotation | BitNullableAnnotation | BitNonNullByDefaultAnnotation; + /** Mark subtypes of Map to analyze dangerous get/remove et al. */ + final int BitMap = 256; + + /** Mark subtypes of Collection to analyze dangerous contains/remove. */ + final int BitCollection = 512; + + /** Mark subtypes of List to analyze dangerous indexOf. */ + final int BitList = 1024; + /** * Set of type bits that should be inherited by any sub types. */ - final int InheritableBits = BitAutoCloseable | BitCloseable | BitUninternedType; + final int InheritableBits = BitAutoCloseable | BitCloseable | BitUninternedType | BitMap | BitCollection | BitList ; } 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 3773f15337..5d40ddfa8e 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 @@ -59,6 +59,7 @@ * Bug 458361 - [1.8][null] reconciler throws NPE in ProblemReporter.illegalReturnRedefinition() * Bug 459967 - [null] compiler should know about nullness of special methods like MyEnum.valueOf() * Bug 461878 - [1.7][1.8][compiler][null] ECJ compiler does not allow to use null annotations on annotations + * Bug 410218 - Optional warning for arguments of "unexpected" types to Map#get(Object), Collection#remove(Object) et al. * Jesper S Moller <jesper@selskabet.org> - Contributions for * bug 382701 - [1.8][compiler] Implement semantic analysis of Lambda expressions & Reference expression * bug 382721 - [1.8][compiler] Effectively final variables needs special treatment @@ -177,6 +178,7 @@ import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding; import org.eclipse.jdt.internal.compiler.lookup.TagBits; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants.DangerousMethod; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.VariableBinding; @@ -609,6 +611,11 @@ public static int getIrritant(int problemID) { case IProblem.UnusedTypeParameter: return CompilerOptions.UnusedTypeParameter; + + case IProblem.UnlikelyCollectionMethodArgumentType: + return CompilerOptions.UnlikelyCollectionMethodArgumentType; + case IProblem.UnlikelyEqualsArgumentType: + return CompilerOptions.UnlikelyEqualsArgumentType; } return 0; } @@ -668,6 +675,8 @@ public static int getProblemCategory(int severity, int problemID) { case CompilerOptions.PotentiallyUnclosedCloseable : case CompilerOptions.PessimisticNullAnalysisForFreeTypeVariables : case CompilerOptions.NonNullTypeVariableFromLegacyInvocation : + case CompilerOptions.UnlikelyCollectionMethodArgumentType : + case CompilerOptions.UnlikelyEqualsArgumentType: return CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM; case CompilerOptions.OverriddenPackageDefaultMethod : @@ -10472,4 +10481,23 @@ public void invalidTypeArguments(TypeReference[] typeReference) { typeReference[0].sourceStart, typeReference[typeReference.length - 1].sourceEnd); } + +public void unlikelyArgumentType(Expression argument, MethodBinding method, TypeBinding argumentType, + TypeBinding receiverType, DangerousMethod dangerousMethod) +{ + this.handle( + dangerousMethod == DangerousMethod.Equals ? IProblem.UnlikelyEqualsArgumentType : IProblem.UnlikelyCollectionMethodArgumentType, + new String[] { + new String(argumentType.readableName()), + new String(method.readableName()), + new String(receiverType.readableName()) + }, + new String[] { + new String(argumentType.shortReadableName()), + new String(method.shortReadableName()), + new String(receiverType.shortReadableName()) + }, + argument.sourceStart, + argument.sourceEnd); +} } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties index fbd87c98a2..9467accb5b 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties @@ -42,6 +42,7 @@ # Bug 439516 - [1.8][null] NonNullByDefault wrongly applied to implicit type bound of binary type # Bug 438467 - [compiler][null] Better error position for "The method _ cannot implement the corresponding method _ due to incompatible nullness constraints" # Bug 458361 - [1.8][null] reconciler throws NPE in ProblemReporter.illegalReturnRedefinition() +# Bug 410218 - Optional warning for arguments of "unexpected" types to Map#get(Object), Collection#remove(Object) et al. # Jesper S Moller <jesper@selskabet.org> - Contributions for # bug 382701 - [1.8][compiler] Implement semantic analysis of Lambda expressions & Reference expression # bug 384567 - [1.5][compiler] Compiler accepts illegal modifiers on package declaration @@ -867,6 +868,10 @@ 1100 = Problem detected during type inference: {0} #1101 is already used up but deprecated 1102 = At least one of the problems in category ''{0}'' is not analysed due to a compiler option being ignored +# more programming problems: +1200 = Unlikely argument type {0} for {1} on a {2} +1201 = Unlikely argument type for equals(): {0} seems to be unrelated to {2} + ### ELABORATIONS ## Access restrictions 78592 = The type ''{1}'' is not API (restriction on classpath entry ''{0}'') @@ -880,4 +885,4 @@ 78602 = The constructor ''{1}'' is not API (restriction on required library ''{0}'') 78604 = The method ''{2}.{1}'' is not API (restriction on classpath entry ''{0}'') 78606 = The method ''{2}.{1}'' is not API (restriction on required library ''{0}'') -78605 = The method ''{2}.{1}'' is not API (restriction on required project ''{0}'')
\ No newline at end of file +78605 = The method ''{2}.{1}'' is not API (restriction on required project ''{0}'') 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 89a651acd3..ffb80ea513 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2016 IBM Corporation and others. + * Copyright (c) 2000, 2017 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 @@ -98,6 +98,8 @@ * COMPILER_INHERIT_NULL_ANNOTATIONS * COMPILER_PB_NONNULL_PARAMETER_ANNOTATION_DROPPED * COMPILER_PB_SYNTACTIC_NULL_ANALYSIS_FOR_FIELDS + * COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE + * COMPILER_PB_UNLIKELY_EQUALS_ARGUMENT_TYPE * Jesper S Moller - Contributions for bug 381345 : [1.8] Take care of the Java 8 major version * - added the following constants: * COMPILER_CODEGEN_METHOD_PARAMETERS_ATTR @@ -1514,6 +1516,60 @@ public final class JavaCore extends Plugin { * @category CompilerOptionID */ public static final String COMPILER_PB_EXPLICITLY_CLOSED_AUTOCLOSEABLE = PLUGIN_ID + ".compiler.problem.explicitlyClosedAutoCloseable"; //$NON-NLS-1$ + + /** + * Compiler option ID: Reporting a method invocation providing an argument of an unlikely type. + * <p>When enabled, the compiler will issue an error or warning when certain well-known Collection methods + * that take an 'Object', like e.g. {@link Map#get(Object)}, are used with an argument type + * that seems to be not related to the corresponding type argument of the Collection.</p> + * <p>By default, this analysis will apply some heuristics to determine whether or not two + * types may or may not be related, which can be changed via option + * {@link #COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE_STRICT}.</p> + * <dl> + * <dt>Option id:</dt><dd><code>"org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType"</code></dd> + * <dt>Possible values:</dt><dd><code>{ "error", "warning", "info", "ignore" }</code></dd> + * <dt>Default:</dt><dd><code>"warning"</code></dd> + * </dl> + * @since 3.13 + * @category CompilerOptionID + */ + public static final String COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE = PLUGIN_ID + ".compiler.problem.unlikelyCollectionMethodArgumentType"; //$NON-NLS-1$ + + /** + * Compiler option ID: Perform strict analysis against the expected type of collection methods. + * <p>This is a sub-option of {@link #COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE}, + * which will replace the heuristics with strict compatibility checks, + * i.e., each argument that is not strictly compatible with the expected type will trigger an error or warning.</p> + * <p>This option has no effect if {@link #COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE} is set to <code>"ignore"</code>.</p> + * <dl> + * <dt>Option id:</dt><dd><code>"org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict"</code></dd> + * <dt>Possible values:</dt><dd><code>{ "enabled", "disabled" }</code></dd> + * <dt>Default:</dt><dd><code>"disabled"</code></dd> + * </dl> + * @since 3.13 + * @category CompilerOptionID + */ + public static final String COMPILER_PB_UNLIKELY_COLLECTION_METHOD_ARGUMENT_TYPE_STRICT = PLUGIN_ID + ".compiler.problem.unlikelyCollectionMethodArgumentTypeStrict"; //$NON-NLS-1$ + + /** + * Compiler option ID: Reporting a method invocation providing an argument of an unlikely type to method 'equals'. + * <p> + * When enabled, the compiler will issue an error or warning when {@link java.lang.Object#equals(Object)} is used with an argument type + * that seems to be not related to the receiver's type, or correspondingly when the arguments of {@link java.util.Objects#equals(Object, Object)} + * have types that seem to be not related to each other. + * </p> + * <dl> + * <dt>Option id:</dt><dd><code>"org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType"</code></dd> + * <dt>Possible values:</dt> + * <dd><code>{ "error", "warning", "info", "ignore" }</code></dd> + * <dt>Default:</dt><dd><code>"info"</code></dd> + * </dl> + * + * @since 3.13 + * @category CompilerOptionID + */ + public static final String COMPILER_PB_UNLIKELY_EQUALS_ARGUMENT_TYPE = PLUGIN_ID + ".compiler.problem.unlikelyEqualsArgumentType"; //$NON-NLS-1$ + /** * Compiler option ID: Annotation-based Null Analysis. * <p>This option controls whether the compiler will use null annotations for |