Merge pull request #8493 from JLLeitschuh/feat/JLL/test_assertion_guard_preconditions

[Java]: Add precondition support for testing library asserts
This commit is contained in:
Chris Smowton
2022-03-31 22:30:09 +01:00
committed by GitHub
13 changed files with 298 additions and 26 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added guard preconditon support for assertion methods for popular testing libraries (e.g. Junit 4, Junit 5, TestNG).

View File

@@ -185,7 +185,7 @@ private module ControlFlowGraphImpl {
* Bind `t` to an unchecked exception that may occur in a precondition check.
*/
private predicate uncheckedExceptionFromMethod(MethodAccess ma, ThrowableType t) {
conditionCheck(ma, _) and
conditionCheckArgument(ma, _, _) and
(t instanceof TypeError or t instanceof TypeRuntimeException)
}

View File

@@ -86,7 +86,7 @@ class Guard extends ExprParent {
or
this instanceof SwitchCase
or
conditionCheck(this, _)
conditionCheckArgument(this, _, _)
}
/** Gets the immediately enclosing callable whose body contains this guard. */
@@ -189,7 +189,7 @@ private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) {
private predicate preconditionBranchEdge(
MethodAccess ma, BasicBlock bb1, BasicBlock bb2, boolean branch
) {
conditionCheck(ma, branch) and
conditionCheckArgument(ma, _, branch) and
bb1.getLastNode() = ma.getControlFlowNode() and
bb2 = bb1.getLastNode().getANormalSuccessor()
}

View File

@@ -57,9 +57,9 @@ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) {
or
g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false
or
exists(MethodAccess check | check = g1 |
conditionCheck(check, _) and
g2 = check.getArgument(0) and
exists(MethodAccess check, int argIndex | check = g1 |
conditionCheckArgument(check, argIndex, _) and
g2 = check.getArgument(argIndex) and
b1 = [true, false] and
b2 = b1
)

View File

@@ -7,20 +7,41 @@
import java
/**
* DEPRECATED: Use `conditionCheckMethodArgument` instead.
* Holds if `m` is a non-overridable method that checks that its first argument
* is equal to `checkTrue` and throws otherwise.
*/
predicate conditionCheckMethod(Method m, boolean checkTrue) {
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Preconditions") and
checkTrue = true and
(m.hasName("checkArgument") or m.hasName("checkState"))
deprecated predicate conditionCheckMethod(Method m, boolean checkTrue) {
conditionCheckMethodArgument(m, 0, checkTrue)
}
/**
* Holds if `m` is a non-overridable method that checks that its zero-indexed `argument`
* is equal to `checkTrue` and throws otherwise.
*/
predicate conditionCheckMethodArgument(Method m, int argument, boolean checkTrue) {
condtionCheckMethodGooglePreconditions(m, checkTrue) and argument = 0
or
m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "Validate") and
checkTrue = true and
(m.hasName("isTrue") or m.hasName("validState"))
conditionCheckMethodApacheCommonsLang3Validate(m, checkTrue) and argument = 0
or
condtionCheckMethodTestingFramework(m, argument, checkTrue)
or
exists(Parameter p, MethodAccess ma, int argIndex, boolean ct, Expr arg |
p = m.getParameter(argument) and
not m.isOverridable() and
m.getBody().getStmt(0).(ExprStmt).getExpr() = ma and
conditionCheckArgument(ma, argIndex, ct) and
ma.getArgument(argIndex) = arg and
(
arg.(LogNotExpr).getExpr().(VarAccess).getVariable() = p and
checkTrue = ct.booleanNot()
or
arg.(VarAccess).getVariable() = p and checkTrue = ct
)
)
or
exists(Parameter p, IfStmt ifstmt, Expr cond |
p = m.getParameter(0) and
p = m.getParameter(argument) and
not m.isOverridable() and
p.getType() instanceof BooleanType and
m.getBody().getStmt(0) = ifstmt and
@@ -35,26 +56,68 @@ predicate conditionCheckMethod(Method m, boolean checkTrue) {
ifstmt.getThen().(SingletonBlock).getStmt() instanceof ThrowStmt
)
)
or
exists(Parameter p, MethodAccess ma, boolean ct, Expr arg |
p = m.getParameter(0) and
not m.isOverridable() and
m.getBody().getStmt(0).(ExprStmt).getExpr() = ma and
conditionCheck(ma, ct) and
ma.getArgument(0) = arg and
}
private predicate condtionCheckMethodGooglePreconditions(Method m, boolean checkTrue) {
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Preconditions") and
checkTrue = true and
(m.hasName("checkArgument") or m.hasName("checkState"))
}
private predicate conditionCheckMethodApacheCommonsLang3Validate(Method m, boolean checkTrue) {
m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "Validate") and
checkTrue = true and
(m.hasName("isTrue") or m.hasName("validState"))
}
/**
* Holds if `m` is a non-overridable testing framework method that checks that its first argument
* is equal to `checkTrue` and throws otherwise.
*/
private predicate condtionCheckMethodTestingFramework(Method m, int argument, boolean checkTrue) {
argument = 0 and
(
m.getDeclaringType().hasQualifiedName("org.junit", "Assume") and
checkTrue = true and
m.hasName("assumeTrue")
or
m.getDeclaringType().hasQualifiedName("org.junit.jupiter.api", "Assertions") and
(
arg.(LogNotExpr).getExpr().(VarAccess).getVariable() = p and
checkTrue = ct.booleanNot()
checkTrue = true and m.hasName("assertTrue")
or
arg.(VarAccess).getVariable() = p and checkTrue = ct
checkTrue = false and m.hasName("assertFalse")
)
or
m.getDeclaringType().hasQualifiedName("org.junit.jupiter.api", "Assumptions") and
(
checkTrue = true and m.hasName("assumeTrue")
or
checkTrue = false and m.hasName("assumeFalse")
)
)
or
m.getDeclaringType().hasQualifiedName(["org.junit", "org.testng"], "Assert") and
m.getParameter(argument).getType() instanceof BooleanType and
(
checkTrue = true and m.hasName("assertTrue")
or
checkTrue = false and m.hasName("assertFalse")
)
}
/**
* DEPRECATED: Use `conditionCheckArgument` instead.
* Holds if `ma` is an access to a non-overridable method that checks that its
* first argument is equal to `checkTrue` and throws otherwise.
*/
predicate conditionCheck(MethodAccess ma, boolean checkTrue) {
conditionCheckMethod(ma.getMethod().getSourceDeclaration(), checkTrue)
deprecated predicate conditionCheck(MethodAccess ma, boolean checkTrue) {
conditionCheckArgument(ma, 0, checkTrue)
}
/**
* Holds if `ma` is an access to a non-overridable method that checks that its
* zero-indexed `argument` is equal to `checkTrue` and throws otherwise.
*/
predicate conditionCheckArgument(MethodAccess ma, int argument, boolean checkTrue) {
conditionCheckMethodArgument(ma.getMethod().getSourceDeclaration(), argument, checkTrue)
}

View File

@@ -47,4 +47,21 @@ public class Logic {
private static void checkFalse(boolean b, String msg) {
checkTrue(!b, msg);
}
void f3(int i) {
checkTrue("i pos", i > 0);
checkFalse("g", g(100));
if (i > 10) {
checkTrue("", i > 20);
}
int dummy = 0;
}
private static void checkTrue(String msg, boolean b) {
if (!b) throw new Error (msg);
}
private static void checkFalse(String msg, boolean b) {
checkTrue(!b, msg);
}
}

View File

@@ -0,0 +1,114 @@
import org.junit.Assert;
import org.junit.jupiter.api.Assertions;
public class Preconditions {
public static void guarded() {}
void test1() {
Assert.assertTrue(true);
guarded();
}
void test2() {
Assert.assertTrue(false);
guarded();
}
void test3() {
Assert.assertFalse(false);
guarded();
}
void test4() {
Assert.assertFalse(true);
guarded();
}
void test5() {
Assert.assertTrue("Reason", true);
guarded();
}
void test6() {
Assert.assertTrue("Reason", false);
guarded();
}
void test7() {
Assert.assertFalse("Reason", false);
guarded();
}
void test8() {
Assert.assertFalse("Reason", true);
guarded();
}
void test9() {
Assertions.assertTrue(true);
guarded();
}
void test10() {
Assertions.assertTrue(false);
guarded();
}
void test11() {
Assertions.assertFalse(false);
guarded();
}
void test12() {
Assertions.assertFalse(true);
guarded();
}
void test13() {
Assertions.assertTrue(true, "Reason");
guarded();
}
void test14() {
Assertions.assertTrue(false, "Reason");
guarded();
}
void test15() {
Assertions.assertFalse(false, "Reason");
guarded();
}
void test16() {
Assertions.assertFalse(true, "Reason");
guarded();
}
void test17() {
t(true);
guarded();
}
void test18() {
t(false);
guarded();
}
void test19() {
f(false);
guarded();
}
void test20() {
f(true);
guarded();
}
static void t(boolean b) {
Assert.assertTrue("Unified Reason", b);
}
static void f(boolean b) {
Assert.assertFalse("Unified Reason", b);
}
}

View File

@@ -46,3 +46,19 @@
| Logic.java:36:16:36:21 | g(...) | false | Logic.java:40:5:40:18 | var ...; |
| Logic.java:37:9:37:14 | ... > ... | true | Logic.java:37:17:39:5 | { ... } |
| Logic.java:44:10:44:10 | b | false | Logic.java:44:33:44:35 | msg |
| Logic.java:52:5:52:29 | checkTrue(...) | true | Logic.java:53:5:53:28 | <Expr>; |
| Logic.java:52:5:52:29 | checkTrue(...) | true | Logic.java:54:5:54:15 | if (...) |
| Logic.java:52:5:52:29 | checkTrue(...) | true | Logic.java:54:17:56:5 | { ... } |
| Logic.java:52:5:52:29 | checkTrue(...) | true | Logic.java:57:5:57:18 | var ...; |
| Logic.java:52:24:52:28 | ... > ... | true | Logic.java:53:5:53:28 | <Expr>; |
| Logic.java:52:24:52:28 | ... > ... | true | Logic.java:54:5:54:15 | if (...) |
| Logic.java:52:24:52:28 | ... > ... | true | Logic.java:54:17:56:5 | { ... } |
| Logic.java:52:24:52:28 | ... > ... | true | Logic.java:57:5:57:18 | var ...; |
| Logic.java:53:5:53:27 | checkFalse(...) | false | Logic.java:54:5:54:15 | if (...) |
| Logic.java:53:5:53:27 | checkFalse(...) | false | Logic.java:54:17:56:5 | { ... } |
| Logic.java:53:5:53:27 | checkFalse(...) | false | Logic.java:57:5:57:18 | var ...; |
| Logic.java:53:21:53:26 | g(...) | false | Logic.java:54:5:54:15 | if (...) |
| Logic.java:53:21:53:26 | g(...) | false | Logic.java:54:17:56:5 | { ... } |
| Logic.java:53:21:53:26 | g(...) | false | Logic.java:57:5:57:18 | var ...; |
| Logic.java:54:9:54:14 | ... > ... | true | Logic.java:54:17:56:5 | { ... } |
| Logic.java:61:10:61:10 | b | false | Logic.java:61:33:61:35 | msg |

View File

@@ -0,0 +1,20 @@
| Preconditions.java:8:9:8:31 | assertTrue(...) | true | Preconditions.java:9:9:9:18 | <Expr>; |
| Preconditions.java:13:9:13:32 | assertTrue(...) | true | Preconditions.java:14:9:14:18 | <Expr>; |
| Preconditions.java:18:9:18:33 | assertFalse(...) | false | Preconditions.java:19:9:19:18 | <Expr>; |
| Preconditions.java:23:9:23:32 | assertFalse(...) | false | Preconditions.java:24:9:24:18 | <Expr>; |
| Preconditions.java:28:9:28:41 | assertTrue(...) | true | Preconditions.java:29:9:29:18 | <Expr>; |
| Preconditions.java:33:9:33:42 | assertTrue(...) | true | Preconditions.java:34:9:34:18 | <Expr>; |
| Preconditions.java:38:9:38:43 | assertFalse(...) | false | Preconditions.java:39:9:39:18 | <Expr>; |
| Preconditions.java:43:9:43:42 | assertFalse(...) | false | Preconditions.java:44:9:44:18 | <Expr>; |
| Preconditions.java:48:9:48:35 | assertTrue(...) | true | Preconditions.java:49:9:49:18 | <Expr>; |
| Preconditions.java:53:9:53:36 | assertTrue(...) | true | Preconditions.java:54:9:54:18 | <Expr>; |
| Preconditions.java:58:9:58:37 | assertFalse(...) | false | Preconditions.java:59:9:59:18 | <Expr>; |
| Preconditions.java:63:9:63:36 | assertFalse(...) | false | Preconditions.java:64:9:64:18 | <Expr>; |
| Preconditions.java:68:9:68:45 | assertTrue(...) | true | Preconditions.java:69:9:69:18 | <Expr>; |
| Preconditions.java:73:9:73:46 | assertTrue(...) | true | Preconditions.java:74:9:74:18 | <Expr>; |
| Preconditions.java:78:9:78:47 | assertFalse(...) | false | Preconditions.java:79:9:79:18 | <Expr>; |
| Preconditions.java:83:9:83:46 | assertFalse(...) | false | Preconditions.java:84:9:84:18 | <Expr>; |
| Preconditions.java:88:9:88:15 | t(...) | true | Preconditions.java:89:9:89:18 | <Expr>; |
| Preconditions.java:93:9:93:16 | t(...) | true | Preconditions.java:94:9:94:18 | <Expr>; |
| Preconditions.java:98:9:98:16 | f(...) | false | Preconditions.java:99:9:99:18 | <Expr>; |
| Preconditions.java:103:9:103:15 | f(...) | false | Preconditions.java:104:9:104:18 | <Expr>; |

View File

@@ -0,0 +1,8 @@
import java
import semmle.code.java.controlflow.Guards
from Guard g, BasicBlock bb, boolean branch
where
g.controls(bb, branch) and
g.getEnclosingCallable().getDeclaringType().hasName("Preconditions")
select g, branch, bb

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/junit-4.11/:${testdir}/../../stubs/junit-jupiter-api-5.2.0/

View File

@@ -12,4 +12,5 @@
| 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<String> for_always | for_always |
| A.java:266:24:266:33 | for_always | Variable $@ is always null here. | A.java:263:5:263:35 | List<String> for_always | for_always |
| A.java:293:5:293:5 | s | Variable $@ is always null here. | A.java:291:5:291:33 | Object s | s |
| B.java:304:7:304:9 | ioe | Variable $@ is always null here. | B.java:297:5:297:25 | Exception ioe | ioe |

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2015-2022 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.jupiter.api;
public class Assertions {
public static void assertTrue(boolean condition) {
}
public static void assertTrue(boolean condition, String message) {
}
public static void assertFalse(boolean condition) {
}
public static void assertFalse(boolean condition, String message) {
}
}