Skip to content

Commit b226cb6

Browse files
authored
Merge pull request #1189 from aschackmull/java/preconditions
Java: Support precondition calls as guards (ODASA-7796).
2 parents f5d52d0 + 9211927 commit b226cb6

File tree

8 files changed

+161
-6
lines changed

8 files changed

+161
-6
lines changed

change-notes/1.21/analysis-java.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Improvements to Java analysis
2+
3+
## New queries
4+
5+
| **Query** | **Tags** | **Purpose** |
6+
|-----------------------------|-----------|--------------------------------------------------------------------|
7+
8+
## Changes to existing queries
9+
10+
| **Query** | **Expected impact** | **Change** |
11+
|----------------------------|------------------------|------------------------------------------------------------------|
12+
13+
## Changes to QL libraries
14+
15+
* The `Guards` library has been extended to account for method calls that check
16+
conditions by conditionally throwing an exception. This includes the
17+
`checkArgument` and `checkState` methods in
18+
`com.google.common.base.Preconditions`, the `isTrue` and `validState` methods
19+
in `org.apache.commons.lang3.Validate`, as well as any similar custom
20+
methods. This means that more guards are recognized yielding precision
21+
improvements in a number of queries including `java/index-out-of-bounds`,
22+
`java/dereferenced-value-may-be-null`, and `java/useless-null-check`.
23+
24+

java/ql/src/semmle/code/java/ControlFlowGraph.qll

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181

8282
import java
8383
private import Completion
84+
private import controlflow.internal.Preconditions
8485

8586
/** A node in the expression-level control-flow graph. */
8687
class ControlFlowNode extends Top, @exprparent {
@@ -169,7 +170,8 @@ private module ControlFlowGraphImpl {
169170
exists(Call c | c = n |
170171
t = c.getCallee().getAThrownExceptionType() or
171172
uncheckedExceptionFromCatch(n, t) or
172-
uncheckedExceptionFromFinally(n, t)
173+
uncheckedExceptionFromFinally(n, t) or
174+
uncheckedExceptionFromMethod(c, t)
173175
)
174176
or
175177
exists(CastExpr c | c = n |
@@ -178,6 +180,14 @@ private module ControlFlowGraphImpl {
178180
)
179181
}
180182

183+
/**
184+
* Bind `t` to an unchecked exception that may occur in a precondition check.
185+
*/
186+
private predicate uncheckedExceptionFromMethod(MethodAccess ma, ThrowableType t) {
187+
conditionCheck(ma, _) and
188+
(t instanceof TypeError or t instanceof TypeRuntimeException)
189+
}
190+
181191
/**
182192
* Bind `t` to an unchecked exception that may transfer control to a finally
183193
* block inside which `n` is nested.

java/ql/src/semmle/code/java/controlflow/Guards.qll

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import java
22
private import semmle.code.java.controlflow.Dominance
33
private import semmle.code.java.controlflow.internal.GuardsLogic
4+
private import semmle.code.java.controlflow.internal.Preconditions
45

56
/**
67
* A basic block that terminates in a condition, splitting the subsequent control flow.
@@ -71,7 +72,7 @@ class ConditionBlock extends BasicBlock {
7172
/**
7273
* A condition that can be evaluated to either true or false. This can either
7374
* be an `Expr` of boolean type that isn't a boolean literal, or a case of a
74-
* switch statement.
75+
* switch statement, or a method access that acts as a precondition check.
7576
*
7677
* Evaluating a switch case to true corresponds to taking that switch case, and
7778
* evaluating it to false corresponds to taking some other branch.
@@ -81,6 +82,8 @@ class Guard extends ExprParent {
8182
this.(Expr).getType() instanceof BooleanType and not this instanceof BooleanLiteral
8283
or
8384
this instanceof SwitchCase
85+
or
86+
conditionCheck(this, _)
8487
}
8588

8689
/** Gets the immediately enclosing callable whose body contains this guard. */
@@ -128,6 +131,8 @@ class Guard extends ExprParent {
128131
pred.(Expr).getParent*() = sc.getSwitch().getExpr() and
129132
bb1 = pred.getBasicBlock()
130133
)
134+
or
135+
preconditionBranchEdge(this, bb1, bb2, branch)
131136
}
132137

133138
/**
@@ -142,6 +147,8 @@ class Guard extends ExprParent {
142147
)
143148
or
144149
switchCaseControls(this, controlled) and branch = true
150+
or
151+
preconditionControls(this, controlled, branch)
145152
}
146153

147154
/**
@@ -165,6 +172,24 @@ private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) {
165172
)
166173
}
167174

175+
private predicate preconditionBranchEdge(
176+
MethodAccess ma, BasicBlock bb1, BasicBlock bb2, boolean branch
177+
) {
178+
conditionCheck(ma, branch) and
179+
bb1.getLastNode() = ma.getControlFlowNode() and
180+
bb2 = bb1.getLastNode().getANormalSuccessor()
181+
}
182+
183+
private predicate preconditionControls(MethodAccess ma, BasicBlock controlled, boolean branch) {
184+
exists(BasicBlock check, BasicBlock succ |
185+
preconditionBranchEdge(ma, check, succ, branch) and
186+
succ.bbDominates(controlled) and
187+
forall(BasicBlock pred | pred = succ.getABBPredecessor() and pred != check |
188+
succ.bbDominates(pred)
189+
)
190+
)
191+
}
192+
168193
/**
169194
* INTERNAL: Use `Guards.controls` instead.
170195
*

java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import java
77
import semmle.code.java.controlflow.Guards
8+
private import Preconditions
89
private import semmle.code.java.dataflow.SSA
910
private import semmle.code.java.dataflow.internal.BaseSSA
1011
private import semmle.code.java.dataflow.NullGuards
@@ -61,6 +62,13 @@ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) {
6162
or
6263
g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false
6364
or
65+
exists(MethodAccess check | check = g1 |
66+
conditionCheck(check, _) and
67+
g2 = check.getArgument(0) and
68+
(b1 = true or b1 = false) and
69+
b2 = b1
70+
)
71+
or
6472
exists(BaseSsaUpdate vbool |
6573
vbool.getAUse() = g1 and
6674
vbool.getDefiningExpr().(VariableAssign).getSource() = g2 and
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Provides predicates for identifying precondition checks like
3+
* `com.google.common.base.Preconditions` and
4+
* `org.apache.commons.lang3.Validate`.
5+
*/
6+
import java
7+
8+
/**
9+
* Holds if `m` is a non-overridable method that checks that its first argument
10+
* is equal to `checkTrue` and throws otherwise.
11+
*/
12+
predicate conditionCheckMethod(Method m, boolean checkTrue) {
13+
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Preconditions") and
14+
checkTrue = true and
15+
(m.hasName("checkArgument") or m.hasName("checkState"))
16+
or
17+
m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "Validate") and
18+
checkTrue = true and
19+
(m.hasName("isTrue") or m.hasName("validState"))
20+
or
21+
exists(Parameter p, IfStmt ifstmt, Expr cond |
22+
p = m.getParameter(0) and
23+
not m.isOverridable() and
24+
p.getType() instanceof BooleanType and
25+
m.getBody().getStmt(0) = ifstmt and
26+
ifstmt.getCondition().getProperExpr() = cond and
27+
(
28+
cond.(LogNotExpr).getExpr().getProperExpr().(VarAccess).getVariable() = p and checkTrue = true
29+
or
30+
cond.(VarAccess).getVariable() = p and checkTrue = false
31+
) and
32+
(
33+
ifstmt.getThen() instanceof ThrowStmt or
34+
ifstmt.getThen().(SingletonBlock).getStmt() instanceof ThrowStmt
35+
)
36+
)
37+
or
38+
exists(Parameter p, MethodAccess ma, boolean ct, Expr arg |
39+
p = m.getParameter(0) and
40+
not m.isOverridable() and
41+
m.getBody().getStmt(0).(ExprStmt).getExpr() = ma and
42+
conditionCheck(ma, ct) and
43+
ma.getArgument(0).getProperExpr() = arg and
44+
(
45+
arg.(LogNotExpr).getExpr().getProperExpr().(VarAccess).getVariable() = p and
46+
checkTrue = ct.booleanNot()
47+
or
48+
arg.(VarAccess).getVariable() = p and checkTrue = ct
49+
)
50+
)
51+
}
52+
53+
/**
54+
* Holds if `ma` is an access to a non-overridable method that checks that its
55+
* first argument is equal to `checkTrue` and throws otherwise.
56+
*/
57+
predicate conditionCheck(MethodAccess ma, boolean checkTrue) {
58+
conditionCheckMethod(ma.getMethod().getSourceDeclaration(), checkTrue)
59+
}

java/ql/src/semmle/code/java/frameworks/Assertions.qll

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ private predicate assertionMethod(Method m, AssertKind kind) {
3939
preconditions.hasQualifiedName("com.google.common.base", "Preconditions")
4040
|
4141
m.hasName("checkNotNull") and kind = AssertKindNotNull()
42-
or
43-
m.hasName("checkArgument") and kind = AssertKindTrue()
44-
or
45-
m.hasName("checkState") and kind = AssertKindTrue()
4642
)
4743
}
4844

java/ql/test/library-tests/guards/Logic.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,21 @@ void f(int[] a, String s) {
3030
if (o instanceof String) {
3131
}
3232
}
33+
34+
void f2(int i) {
35+
checkTrue(i > 0, "i pos");
36+
checkFalse(g(100), "g");
37+
if (i > 10) {
38+
checkTrue(i > 20, "");
39+
}
40+
int dummy = 0;
41+
}
42+
43+
private static void checkTrue(boolean b, String msg) {
44+
if (!b) throw new Exception(msg);
45+
}
46+
47+
private static void checkFalse(boolean b, String msg) {
48+
checkTrue(!b, msg);
49+
}
3350
}

java/ql/test/library-tests/guards/guardslogic.expected

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,19 @@
3030
| Logic.java:29:16:29:19 | g(...) | false | Logic.java:30:30:31:5 | stmt |
3131
| Logic.java:29:16:29:19 | g(...) | true | Logic.java:29:23:29:26 | null |
3232
| Logic.java:30:9:30:27 | ...instanceof... | true | Logic.java:30:30:31:5 | stmt |
33+
| Logic.java:35:5:35:29 | checkTrue(...) | true | Logic.java:36:5:36:28 | stmt |
34+
| Logic.java:35:5:35:29 | checkTrue(...) | true | Logic.java:37:5:37:15 | stmt |
35+
| Logic.java:35:5:35:29 | checkTrue(...) | true | Logic.java:37:17:39:5 | stmt |
36+
| Logic.java:35:5:35:29 | checkTrue(...) | true | Logic.java:40:5:40:18 | stmt |
37+
| Logic.java:35:15:35:19 | ... > ... | true | Logic.java:36:5:36:28 | stmt |
38+
| Logic.java:35:15:35:19 | ... > ... | true | Logic.java:37:5:37:15 | stmt |
39+
| Logic.java:35:15:35:19 | ... > ... | true | Logic.java:37:17:39:5 | stmt |
40+
| Logic.java:35:15:35:19 | ... > ... | true | Logic.java:40:5:40:18 | stmt |
41+
| Logic.java:36:5:36:27 | checkFalse(...) | false | Logic.java:37:5:37:15 | stmt |
42+
| Logic.java:36:5:36:27 | checkFalse(...) | false | Logic.java:37:17:39:5 | stmt |
43+
| Logic.java:36:5:36:27 | checkFalse(...) | false | Logic.java:40:5:40:18 | stmt |
44+
| Logic.java:36:16:36:21 | g(...) | false | Logic.java:37:5:37:15 | stmt |
45+
| Logic.java:36:16:36:21 | g(...) | false | Logic.java:37:17:39:5 | stmt |
46+
| Logic.java:36:16:36:21 | g(...) | false | Logic.java:40:5:40:18 | stmt |
47+
| Logic.java:37:9:37:14 | ... > ... | true | Logic.java:37:17:39:5 | stmt |
48+
| Logic.java:44:10:44:10 | b | false | Logic.java:44:33:44:35 | msg |

0 commit comments

Comments
 (0)