diff --git a/java/ql/src/semmle/code/java/dataflow/Nullness.qll b/java/ql/src/semmle/code/java/dataflow/Nullness.qll index 8dbac1520ba..af8e323521c 100644 --- a/java/ql/src/semmle/code/java/dataflow/Nullness.qll +++ b/java/ql/src/semmle/code/java/dataflow/Nullness.qll @@ -147,7 +147,12 @@ private ControlFlowNode ensureNotNull(SsaVariable v) { result.(AssertStmt).getExpr() = nullGuard(v, true, false) or exists(AssertTrueMethod m | result = m.getACheck(nullGuard(v, true, false))) or exists(AssertFalseMethod m | result = m.getACheck(nullGuard(v, false, false))) or - exists(AssertNotNullMethod m | result = m.getACheck(v.getAUse())) + exists(AssertNotNullMethod m | result = m.getACheck(v.getAUse())) or + exists(AssertThatMethod m, MethodAccess ma | + result = m.getACheck(v.getAUse()) and ma.getControlFlowNode() = result + | + ma.getAnArgument().(MethodAccess).getMethod().getName() = "notNullValue" + ) } /** diff --git a/java/ql/src/semmle/code/java/frameworks/Assertions.qll b/java/ql/src/semmle/code/java/frameworks/Assertions.qll index e2276ccb3b3..aac514a26bd 100644 --- a/java/ql/src/semmle/code/java/frameworks/Assertions.qll +++ b/java/ql/src/semmle/code/java/frameworks/Assertions.qll @@ -11,7 +11,8 @@ newtype AssertKind = AssertKindTrue() or AssertKindFalse() or AssertKindNotNull() or - AssertKindFail() + AssertKindFail() or + AssertKindThat() private predicate assertionMethod(Method m, AssertKind kind) { exists(RefType junit | @@ -27,6 +28,13 @@ private predicate assertionMethod(Method m, AssertKind kind) { m.hasName("fail") and kind = AssertKindFail() ) or + exists(RefType hamcrest | + m.getDeclaringType() = hamcrest and + hamcrest.hasQualifiedName("org.hamcrest", "MatcherAssert") // TODO: Check some older versions of hamcrest + | + m.hasName("assertThat") and kind = AssertKindThat() + ) + or exists(RefType objects | m.getDeclaringType() = objects and objects.hasQualifiedName("java.util", "Objects") @@ -82,6 +90,14 @@ class AssertFailMethod extends AssertionMethod { AssertFailMethod() { assertionMethod(this, AssertKindFail()) } } +/** + * A method that asserts that its first argument has a property + * given by its second argument. + */ +class AssertThatMethod extends AssertionMethod { + AssertThatMethod() { assertionMethod(this, AssertKindThat()) } +} + /** A trivially failing assertion. That is, `assert false` or its equivalents. */ predicate assertFail(BasicBlock bb, ControlFlowNode n) { bb = n.getBasicBlock() and diff --git a/java/ql/test/query-tests/Nullness/A.java b/java/ql/test/query-tests/Nullness/A.java index 0dcb5304b77..2a3f36da4b1 100644 --- a/java/ql/test/query-tests/Nullness/A.java +++ b/java/ql/test/query-tests/Nullness/A.java @@ -1,6 +1,8 @@ import java.util.Iterator; import java.util.List; import static org.junit.Assert.*; +import static org.hamcrest.core.IsNull.*; +import static org.hamcrest.MatcherAssert.*; public class A { public void notTest() { @@ -296,6 +298,12 @@ public class A { for (it = iter.iterator(); !!it.hasNext(); ) {} } + public void assertThatTest() { + Object assertThat_ok1 = maybe() ? null : new Object(); + assertThat(assertThat_ok1, notNullValue()); + assertThat_ok1.toString(); + } + private boolean m; A(boolean m) { this.m = m; diff --git a/java/ql/test/query-tests/Nullness/NullAlways.expected b/java/ql/test/query-tests/Nullness/NullAlways.expected index 31feb5ad447..2344f849ab3 100644 --- a/java/ql/test/query-tests/Nullness/NullAlways.expected +++ b/java/ql/test/query-tests/Nullness/NullAlways.expected @@ -1,15 +1,15 @@ -| A.java:13:7:13:9 | not | Variable $@ is always null here. | A.java:11:5:11:22 | Object not | not | -| A.java:95:18:95:36 | synchronized_always | Variable $@ is always null here. | A.java:94:5:94:38 | Object synchronized_always | synchronized_always | -| A.java:159:26:159:34 | do_always | Variable $@ is always null here. | A.java:157:5:157:28 | String do_always | do_always | -| A.java:165:26:165:34 | do_maybe1 | Variable $@ is always null here. | A.java:163:5:163:28 | String do_maybe1 | do_maybe1 | -| A.java:185:26:185:37 | while_always | Variable $@ is always null here. | A.java:183:5:183:31 | String while_always | while_always | -| A.java:205:26:205:34 | if_always | Variable $@ is always null here. | A.java:203:5:203:28 | String if_always | if_always | -| A.java:221:24:221:29 | for_ok | Variable $@ is always null here. | A.java:217:5:217:19 | String for_ok | for_ok | -| A.java:224:26:224:35 | for_always | Variable $@ is always null here. | A.java:223:10:223:33 | String for_always | for_always | -| A.java:234:5:234:14 | array_null | Variable $@ is always null here. | A.java:233:5:233:28 | int[] array_null | array_null | -| A.java:246:24:246:34 | arrayaccess | Variable $@ is always null here. | A.java:242:5:242:29 | int[] arrayaccess | arrayaccess | -| A.java:247:24:247:34 | fieldaccess | Variable $@ is always null here. | A.java:243:5:243:32 | String[] fieldaccess | fieldaccess | -| A.java:248:24:248:35 | methodaccess | Variable $@ is always null here. | A.java:244:5:244:31 | Object methodaccess | methodaccess | -| A.java:262:21:262:30 | for_always | Variable $@ is always null here. | A.java:261:5:261:35 | List for_always | for_always | -| A.java:264:24:264:33 | for_always | Variable $@ is always null here. | A.java:261:5:261:35 | List for_always | for_always | +| A.java:15:7:15:9 | not | Variable $@ is always null here. | A.java:13:5:13:22 | Object not | not | +| A.java:97:18:97:36 | synchronized_always | Variable $@ is always null here. | A.java:96:5:96:38 | Object synchronized_always | synchronized_always | +| A.java:161:26:161:34 | do_always | Variable $@ is always null here. | A.java:159:5:159:28 | String do_always | do_always | +| A.java:167:26:167:34 | do_maybe1 | Variable $@ is always null here. | A.java:165:5:165:28 | String do_maybe1 | do_maybe1 | +| A.java:187:26:187:37 | while_always | Variable $@ is always null here. | A.java:185:5:185:31 | String while_always | while_always | +| A.java:207:26:207:34 | if_always | Variable $@ is always null here. | A.java:205:5:205:28 | String if_always | if_always | +| A.java:223:24:223:29 | for_ok | Variable $@ is always null here. | A.java:219:5:219:19 | String for_ok | for_ok | +| A.java:226:26:226:35 | for_always | Variable $@ is always null here. | A.java:225:10:225:33 | String for_always | for_always | +| A.java:236:5:236:14 | array_null | Variable $@ is always null here. | A.java:235:5:235:28 | int[] array_null | array_null | +| A.java:248:24:248:34 | arrayaccess | Variable $@ is always null here. | A.java:244:5:244:29 | int[] arrayaccess | arrayaccess | +| A.java:249:24:249:34 | fieldaccess | Variable $@ is always null here. | A.java:245:5:245:32 | String[] fieldaccess | fieldaccess | +| A.java:250:24:250:35 | methodaccess | Variable $@ is always null here. | A.java:246:5:246:31 | Object methodaccess | methodaccess | +| A.java:264:21:264:30 | for_always | Variable $@ is always null here. | A.java:263:5:263:35 | List for_always | for_always | +| A.java:266:24:266:33 | for_always | Variable $@ is always null here. | A.java:263:5:263:35 | List for_always | for_always | | B.java:304:7:304:9 | ioe | Variable $@ is always null here. | B.java:297:5:297:25 | Exception ioe | ioe | diff --git a/java/ql/test/query-tests/Nullness/NullMaybe.expected b/java/ql/test/query-tests/Nullness/NullMaybe.expected index 4a21a3f2f66..5d50a47b9d8 100644 --- a/java/ql/test/query-tests/Nullness/NullMaybe.expected +++ b/java/ql/test/query-tests/Nullness/NullMaybe.expected @@ -1,9 +1,9 @@ -| A.java:46:5:46:21 | assertNotNull_ok3 | Variable $@ may be null here because of $@ assignment. | A.java:44:5:44:61 | Object assertNotNull_ok3 | assertNotNull_ok3 | A.java:44:12:44:60 | assertNotNull_ok3 | this | -| A.java:170:26:170:33 | do_maybe | Variable $@ may be null here because of $@ assignment. | A.java:168:5:168:25 | String do_maybe | do_maybe | A.java:171:7:171:21 | ...=... | this | -| A.java:191:26:191:36 | while_maybe | Variable $@ may be null here because of $@ assignment. | A.java:189:5:189:28 | String while_maybe | while_maybe | A.java:192:7:192:24 | ...=... | this | -| A.java:213:24:213:31 | if_maybe | Variable $@ may be null here because of $@ assignment. | A.java:209:5:209:25 | String if_maybe | if_maybe | A.java:211:7:211:21 | ...=... | this | -| A.java:228:26:228:34 | for_maybe | Variable $@ may be null here because of $@ assignment. | A.java:227:10:227:30 | String for_maybe | for_maybe | A.java:227:35:227:50 | ...=... | this | -| A.java:271:24:271:32 | for_maybe | Variable $@ may be null here because of $@ assignment. | A.java:266:5:266:63 | List for_maybe | for_maybe | A.java:269:7:269:22 | ...=... | this | +| A.java:48:5:48:21 | assertNotNull_ok3 | Variable $@ may be null here because of $@ assignment. | A.java:46:5:46:61 | Object assertNotNull_ok3 | assertNotNull_ok3 | A.java:46:12:46:60 | assertNotNull_ok3 | this | +| A.java:172:26:172:33 | do_maybe | Variable $@ may be null here because of $@ assignment. | A.java:170:5:170:25 | String do_maybe | do_maybe | A.java:173:7:173:21 | ...=... | this | +| A.java:193:26:193:36 | while_maybe | Variable $@ may be null here because of $@ assignment. | A.java:191:5:191:28 | String while_maybe | while_maybe | A.java:194:7:194:24 | ...=... | this | +| A.java:215:24:215:31 | if_maybe | Variable $@ may be null here because of $@ assignment. | A.java:211:5:211:25 | String if_maybe | if_maybe | A.java:213:7:213:21 | ...=... | this | +| A.java:230:26:230:34 | for_maybe | Variable $@ may be null here because of $@ assignment. | A.java:229:10:229:30 | String for_maybe | for_maybe | A.java:229:35:229:50 | ...=... | this | +| A.java:273:24:273:32 | for_maybe | Variable $@ may be null here because of $@ assignment. | A.java:268:5:268:63 | List for_maybe | for_maybe | A.java:271:7:271:22 | ...=... | this | | B.java:16:5:16:9 | param | Variable $@ may be null here because of $@ null argument. | B.java:15:23:15:34 | param | param | B.java:11:13:11:16 | null | this | | B.java:23:5:23:9 | param | Variable $@ may be null here as suggested by $@ null guard. | B.java:19:23:19:34 | param | param | B.java:20:9:20:21 | ... != ... | this | | B.java:57:7:57:8 | o7 | Variable $@ may be null here because of $@ assignment. | B.java:52:5:52:34 | Object o7 | o7 | B.java:52:12:52:33 | o7 | this | diff --git a/java/ql/test/query-tests/Nullness/options b/java/ql/test/query-tests/Nullness/options index e33d1dca688..b996e88e0b0 100644 --- a/java/ql/test/query-tests/Nullness/options +++ b/java/ql/test/query-tests/Nullness/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/junit-4.11:${testdir}/../../stubs/junit-jupiter-api-5.2.0 +//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/junit-4.11:${testdir}/../../stubs/hamcrest-2.2:${testdir}/../../stubs/junit-jupiter-api-5.2.0 diff --git a/java/ql/test/stubs/hamcrest-2.2/LICENSE.txt b/java/ql/test/stubs/hamcrest-2.2/LICENSE.txt new file mode 100644 index 00000000000..4933bda5bac --- /dev/null +++ b/java/ql/test/stubs/hamcrest-2.2/LICENSE.txt @@ -0,0 +1,27 @@ +BSD License + +Copyright (c) 2000-2015 www.hamcrest.org +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. Redistributions in binary form must reproduce +the above copyright notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. + +Neither the name of Hamcrest nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/java/ql/test/stubs/hamcrest-2.2/org/hamcrest/MatcherAssert.java b/java/ql/test/stubs/hamcrest-2.2/org/hamcrest/MatcherAssert.java new file mode 100644 index 00000000000..b6deec029c6 --- /dev/null +++ b/java/ql/test/stubs/hamcrest-2.2/org/hamcrest/MatcherAssert.java @@ -0,0 +1,14 @@ +package org.hamcrest; + + +public class MatcherAssert { + public static void assertThat(T actual, Object matcher) { + assertThat("", actual, matcher); + } + + public static void assertThat(String reason, T actual, Object matcher) { + } + + public static void assertThat(String reason, boolean assertion) { + } +} diff --git a/java/ql/test/stubs/hamcrest-2.2/org/hamcrest/core/IsNull.java b/java/ql/test/stubs/hamcrest-2.2/org/hamcrest/core/IsNull.java new file mode 100644 index 00000000000..3bb6a345734 --- /dev/null +++ b/java/ql/test/stubs/hamcrest-2.2/org/hamcrest/core/IsNull.java @@ -0,0 +1,13 @@ +package org.hamcrest.core; + +public class IsNull { + + public static Object notNullValue() { + return new String(); + } + + public static Object notNullValue(Class type) { + return new String(); + } +} +