Skip to content

Commit 5c53249

Browse files
committed
Java: Add ModulusAnalysis.
1 parent e7b0d39 commit 5c53249

File tree

4 files changed

+351
-8
lines changed

4 files changed

+351
-8
lines changed
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
/**
2+
* Provides inferences of the form: `e` equals `b + v` modulo `m` where `e` is
3+
* an expression, `b` is a `Bound` (typically zero or the value of an SSA
4+
* variable), and `v` is an integer in the range `[0 .. m-1]`.
5+
*/
6+
7+
import java
8+
private import SSA
9+
private import RangeUtils
10+
private import semmle.code.java.controlflow.Guards
11+
import Bound
12+
13+
/**
14+
* Holds if `e + delta` equals `v` at `pos`.
15+
*/
16+
private predicate valueFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta) {
17+
ssaUpdateStep(v, e, delta) and pos.hasReadOfVar(v)
18+
or
19+
exists(Guard guard, boolean testIsTrue |
20+
pos.hasReadOfVar(v) and
21+
guard = eqFlowCond(v, e, delta, true, testIsTrue) and
22+
guardDirectlyControlsSsaRead(guard, pos, testIsTrue)
23+
)
24+
}
25+
26+
/**
27+
* Holds if `add` is the addition of `larg` and `rarg`, neither of which are
28+
* `ConstantIntegerExpr`s.
29+
*/
30+
private predicate nonConstAddition(Expr add, Expr larg, Expr rarg) {
31+
(
32+
exists(AddExpr a | a = add |
33+
larg = a.getLeftOperand() and
34+
rarg = a.getRightOperand()
35+
) or
36+
exists(AssignAddExpr a | a = add |
37+
larg = a.getDest() and
38+
rarg = a.getRhs()
39+
)
40+
) and
41+
not larg instanceof ConstantIntegerExpr and
42+
not rarg instanceof ConstantIntegerExpr
43+
}
44+
45+
/**
46+
* Holds if `sub` is the subtraction of `larg` and `rarg`, where `rarg` is not
47+
* a `ConstantIntegerExpr`.
48+
*/
49+
private predicate nonConstSubtraction(Expr sub, Expr larg, Expr rarg) {
50+
(
51+
exists(SubExpr s | s = sub |
52+
larg = s.getLeftOperand() and
53+
rarg = s.getRightOperand()
54+
) or
55+
exists(AssignSubExpr s | s = sub |
56+
larg = s.getDest() and
57+
rarg = s.getRhs()
58+
)
59+
) and
60+
not rarg instanceof ConstantIntegerExpr
61+
}
62+
63+
/** Gets an expression that is the remainder modulo `mod` of `arg`. */
64+
private Expr modExpr(Expr arg, int mod) {
65+
exists(RemExpr rem |
66+
result = rem and
67+
arg = rem.getLeftOperand() and
68+
rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = mod and
69+
mod >= 2
70+
) or
71+
exists(CompileTimeConstantExpr c |
72+
mod = 2.pow([1..30]) and
73+
c.getIntValue() = mod - 1 and
74+
result.(AndBitwiseExpr).hasOperands(arg, c)
75+
) or
76+
result.(ParExpr).getExpr() = modExpr(arg, mod)
77+
}
78+
79+
/**
80+
* Gets a guard that tests whether `v` is congruent with `val` modulo `mod` on
81+
* its `testIsTrue` branch.
82+
*/
83+
private Guard moduloCheck(SsaVariable v, int val, int mod, boolean testIsTrue) {
84+
exists(Expr rem, CompileTimeConstantExpr c, int r, boolean polarity |
85+
result.isEquality(rem, c, polarity) and
86+
c.getIntValue() = r and
87+
rem = modExpr(v.getAUse(), mod) and
88+
(
89+
testIsTrue = polarity and val = r
90+
or
91+
testIsTrue = polarity.booleanNot() and mod = 2 and val = 1 - r and
92+
(r = 0 or r = 1)
93+
)
94+
)
95+
}
96+
97+
/**
98+
* Holds if a guard ensures that `v` at `pos` is congruent with `val` modulo `mod`.
99+
*/
100+
private predicate moduloGuardedRead(SsaVariable v, SsaReadPosition pos, int val, int mod) {
101+
exists(Guard guard, boolean testIsTrue |
102+
pos.hasReadOfVar(v) and
103+
guard = moduloCheck(v, val, mod, testIsTrue) and
104+
guardControlsSsaRead(guard, pos, testIsTrue)
105+
)
106+
}
107+
108+
/** Holds if `factor` is a power of 2 that divides `mask`. */
109+
bindingset[mask]
110+
private predicate andmaskFactor(int mask, int factor) {
111+
mask % factor = 0 and
112+
factor = 2.pow([1..30])
113+
}
114+
115+
/** Holds if `e` is evenly divisible by `factor`. */
116+
private predicate evenlyDivisibleExpr(Expr e, int factor) {
117+
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() |
118+
e.(MulExpr).getAnOperand() = c and factor = k.abs() and factor >= 2 or
119+
e.(AssignMulExpr).getSource() = c and factor = k.abs() and factor >= 2 or
120+
e.(LShiftExpr).getRightOperand() = c and factor = 2.pow(k) and k > 0 or
121+
e.(AssignLShiftExpr).getRhs() = c and factor = 2.pow(k) and k > 0 or
122+
e.(AndBitwiseExpr).getAnOperand() = c and factor = max(int f | andmaskFactor(k, f)) or
123+
e.(AssignAndExpr).getSource() = c and factor = max(int f | andmaskFactor(k, f))
124+
)
125+
}
126+
127+
private predicate id(BasicBlock x, BasicBlock y) { x = y }
128+
129+
private predicate idOf(BasicBlock x, int y) = equivalenceRelation(id/2)(x, y)
130+
131+
private int getId(BasicBlock bb) { idOf(bb, result) }
132+
133+
/**
134+
* Holds if `inp` is an input to `phi` along `edge` and this input has index `r`
135+
* in an arbitrary 1-based numbering of the input edges to `phi`.
136+
*/
137+
private predicate rankedPhiInput(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int r) {
138+
edge.phiInput(phi, inp) and
139+
edge = rank[r](SsaReadPositionPhiInputEdge e | e.phiInput(phi, _) | e order by getId(e.getOrigBlock()))
140+
}
141+
142+
/**
143+
* Holds if `rix` is the number of input edges to `phi`.
144+
*/
145+
private predicate maxPhiInputRank(SsaPhiNode phi, int rix) {
146+
rix = max(int r | rankedPhiInput(phi, _, _, r))
147+
}
148+
149+
private int gcdLim() { result = 128 }
150+
151+
/**
152+
* Gets the greatest common divisor of `x` and `y`. This is restricted to small
153+
* inputs and the case when `x` and `y` are not relatively prime.
154+
*/
155+
private int gcd(int x, int y) {
156+
result != 1 and
157+
result = x.abs() and y = 0 and x in [-gcdLim()..gcdLim()]
158+
or
159+
result = gcd(y, x % y) and y != 0 and x in [-gcdLim()..gcdLim()]
160+
}
161+
162+
/**
163+
* Gets the remainder of `val` modulo `mod`.
164+
*
165+
* For `mod = 0` the result equals `val` and for `mod > 1` the result is within
166+
* the range `[0 .. mod-1]`.
167+
*/
168+
bindingset[val, mod]
169+
private int remainder(int val, int mod) {
170+
mod = 0 and result = val or
171+
mod > 1 and result = ((val % mod) + mod) % mod
172+
}
173+
174+
/**
175+
* Holds if `inp` is an input to `phi` and equals `phi` modulo `mod` along `edge`.
176+
*/
177+
private predicate phiSelfModulus(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int mod) {
178+
exists(SsaBound phibound, int v, int m |
179+
edge.phiInput(phi, inp) and
180+
phibound.getSsa() = phi and
181+
ssaModulus(inp, edge, phibound, v, m) and
182+
mod = gcd(m, v) and
183+
mod != 1
184+
)
185+
}
186+
187+
/**
188+
* Holds if `b + val` modulo `mod` is a candidate congruence class for `phi`.
189+
*/
190+
private predicate phiModulusInit(SsaPhiNode phi, Bound b, int val, int mod) {
191+
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge |
192+
edge.phiInput(phi, inp) and
193+
ssaModulus(inp, edge, b, val, mod)
194+
)
195+
}
196+
197+
/**
198+
* Holds if all inputs to `phi` numbered `1` to `rix` are equal to `b + val` modulo `mod`.
199+
*/
200+
private predicate phiModulusRankStep(SsaPhiNode phi, Bound b, int val, int mod, int rix) {
201+
rix = 0 and
202+
phiModulusInit(phi, b, val, mod)
203+
or
204+
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge, int v1, int m1 |
205+
mod != 1 and
206+
val = remainder(v1, mod)
207+
|
208+
exists(int v2, int m2 |
209+
rankedPhiInput(phi, inp, edge, rix) and
210+
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
211+
ssaModulus(inp, edge, b, v2, m2) and
212+
mod = gcd(gcd(m1, m2), v1 - v2)
213+
)
214+
or
215+
exists(int m2 |
216+
rankedPhiInput(phi, inp, edge, rix) and
217+
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
218+
phiSelfModulus(phi, inp, edge, m2) and
219+
mod = gcd(m1, m2)
220+
)
221+
)
222+
}
223+
224+
/**
225+
* Holds if `phi` is equal to `b + val` modulo `mod`.
226+
*/
227+
private predicate phiModulus(SsaPhiNode phi, Bound b, int val, int mod) {
228+
exists(int r |
229+
maxPhiInputRank(phi, r) and
230+
phiModulusRankStep(phi, b, val, mod, r)
231+
)
232+
}
233+
234+
/**
235+
* Holds if `v` at `pos` is equal to `b + val` modulo `mod`.
236+
*/
237+
private predicate ssaModulus(SsaVariable v, SsaReadPosition pos, Bound b, int val, int mod) {
238+
phiModulus(v, b, val, mod) and pos.hasReadOfVar(v)
239+
or
240+
b.(SsaBound).getSsa() = v and pos.hasReadOfVar(v) and val = 0 and mod = 0
241+
or
242+
exists(Expr e, int val0, int delta |
243+
exprModulus(e, b, val0, mod) and
244+
valueFlowStepSsa(v, pos, e, delta) and
245+
val = remainder(val0 + delta, mod)
246+
)
247+
or
248+
moduloGuardedRead(v, pos, val, mod) and b instanceof ZeroBound
249+
}
250+
251+
/**
252+
* Holds if `e` is equal to `b + val` modulo `mod`.
253+
*
254+
* There are two cases for the modulus:
255+
* - `mod = 0`: The equality `e = b + val` is an ordinary equality.
256+
* - `mod > 1`: `val` lies within the range `[0 .. mod-1]`.
257+
*/
258+
cached
259+
predicate exprModulus(Expr e, Bound b, int val, int mod) {
260+
e = b.getExpr(val) and mod = 0 or
261+
evenlyDivisibleExpr(e, mod) and val = 0 and b instanceof ZeroBound or
262+
exists(SsaVariable v, SsaReadPositionBlock bb |
263+
ssaModulus(v, bb, b, val, mod) and
264+
e = v.getAUse() and
265+
bb.getBlock() = e.getBasicBlock()
266+
) or
267+
exists(Expr mid, int val0, int delta |
268+
exprModulus(mid, b, val0, mod) and
269+
valueFlowStep(e, mid, delta) and
270+
val = remainder(val0 + delta, mod)
271+
) or
272+
exists(ConditionalExpr cond, int v1, int v2, int m1, int m2 |
273+
cond = e and
274+
condExprBranchModulus(cond, true, b, v1, m1) and
275+
condExprBranchModulus(cond, false, b, v2, m2) and
276+
mod = gcd(gcd(m1, m2), v1 - v2) and
277+
mod != 1 and
278+
val = remainder(v1, mod)
279+
) or
280+
exists(Bound b1, Bound b2, int v1, int v2, int m1, int m2 |
281+
addModulus(e, true, b1, v1, m1) and
282+
addModulus(e, false, b2, v2, m2) and
283+
mod = gcd(m1, m2) and
284+
mod != 1 and
285+
val = remainder(v1 + v2, mod)
286+
|
287+
b = b1 and b2 instanceof ZeroBound or
288+
b = b2 and b1 instanceof ZeroBound
289+
) or
290+
exists(int v1, int v2, int m1, int m2 |
291+
subModulus(e, true, b, v1, m1) and
292+
subModulus(e, false, any(ZeroBound zb), v2, m2) and
293+
mod = gcd(m1, m2) and
294+
mod != 1 and
295+
val = remainder(v1 - v2, mod)
296+
)
297+
}
298+
299+
private predicate condExprBranchModulus(ConditionalExpr cond, boolean branch, Bound b, int val, int mod) {
300+
exprModulus(cond.getTrueExpr(), b, val, mod) and branch = true or
301+
exprModulus(cond.getFalseExpr(), b, val, mod) and branch = false
302+
}
303+
304+
private predicate addModulus(Expr add, boolean isLeft, Bound b, int val, int mod) {
305+
exists(Expr larg, Expr rarg |
306+
nonConstAddition(add, larg, rarg)
307+
|
308+
exprModulus(larg, b, val, mod) and isLeft = true
309+
or
310+
exprModulus(rarg, b, val, mod) and isLeft = false
311+
)
312+
}
313+
314+
private predicate subModulus(Expr sub, boolean isLeft, Bound b, int val, int mod) {
315+
exists(Expr larg, Expr rarg |
316+
nonConstSubtraction(sub, larg, rarg)
317+
|
318+
exprModulus(larg, b, val, mod) and isLeft = true
319+
or
320+
exprModulus(rarg, b, val, mod) and isLeft = false
321+
)
322+
}

java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ private import SSA
6868
private import RangeUtils
6969
private import semmle.code.java.controlflow.internal.GuardsLogic
7070
private import SignAnalysis
71-
private import ParityAnalysis
71+
private import ModulusAnalysis
7272
private import semmle.code.java.Reflection
7373
private import semmle.code.java.Collections
7474
private import semmle.code.java.Maps
@@ -133,6 +133,29 @@ private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int
133133
)
134134
}
135135

136+
/**
137+
* Holds if `comp` is a comparison between `x` and `y` for which `y - x` has a
138+
* fixed value modulo some `mod > 1`, such that the comparison can be
139+
* strengthened by `strengthen` when evaluating to `testIsTrue`.
140+
*/
141+
private predicate modulusComparison(ComparisonExpr comp, boolean testIsTrue, int strengthen) {
142+
exists(Bound b, int v1, int v2, int mod, boolean resultIsStrict, int d, int k |
143+
// If `x <= y` and `x =(mod) b + v1` and `y =(mod) b + v2` then
144+
// `0 <= y - x =(mod) v2 - v1`. By choosing `k =(mod) v2 - v1` with
145+
// `0 <= k < mod` we get `k <= y - x`. If the resulting comparison is
146+
// strict then the strengthening amount is instead `k - 1` modulo `mod`:
147+
// `x < y` means `0 <= y - x - 1 =(mod) k - 1` so `k - 1 <= y - x - 1` and
148+
// thus `k - 1 < y - x` with `0 <= k - 1 < mod`.
149+
exprModulus(comp.getLesserOperand(), b, v1, mod) and
150+
exprModulus(comp.getGreaterOperand(), b, v2, mod) and
151+
(testIsTrue = true or testIsTrue = false) and
152+
(if comp.isStrict() then resultIsStrict = testIsTrue else resultIsStrict = testIsTrue.booleanNot()) and
153+
(resultIsStrict = true and d = 1 or resultIsStrict = false and d = 0) and
154+
(testIsTrue = true and k = v2 - v1 or testIsTrue = false and k = v1 - v2) and
155+
strengthen = (((k - d) % mod) + mod) % mod
156+
)
157+
}
158+
136159
/**
137160
* Gets a condition that tests whether `v` is bounded by `e + delta`.
138161
*
@@ -152,10 +175,10 @@ private Guard boundFlowCond(SsaVariable v, Expr e, int delta, boolean upper, boo
152175
upper = false and strengthen = 1)
153176
else
154177
strengthen = 0) and
155-
// A non-strict inequality `x <= y` can be strengthened to `x <= y - 1` if
156-
// `x` and `y` have opposite parities, and a strict inequality `x < y` can
157-
// be similarly strengthened if `x` and `y` have equal parities.
158-
(if parityComparison(comp, resultIsStrict) then d2 = strengthen else d2 = 0) and
178+
(
179+
exists(int k | modulusComparison(comp, testIsTrue, k) and d2 = strengthen * k) or
180+
not modulusComparison(comp, testIsTrue, _) and d2 = 0
181+
) and
159182
// A strict inequality `x < y` can be strengthened to `x <= y - 1`.
160183
(resultIsStrict = true and d3 = strengthen or resultIsStrict = false and d3 = 0) and
161184
delta = d1 + d2 + d3

java/ql/test/query-tests/RangeAnalysis/A.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void m5(int n) {
7676
int[] a = new int[3 * n];
7777
int sum = 0;
7878
for (int i = 0; i < a.length; i += 3) {
79-
sum += a[i] + a[i + 1] + a[i + 2]; // OK - FP
79+
sum += a[i] + a[i + 1] + a[i + 2]; // OK
8080
}
8181
}
8282

java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
| A.java:46:14:46:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
66
| A.java:55:14:55:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
77
| A.java:64:14:64:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
8-
| A.java:79:21:79:28 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
9-
| A.java:79:32:79:39 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 1. |
108
| A.java:86:12:86:16 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
119
| A.java:97:18:97:31 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 8. |
1210
| A.java:110:14:110:21 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |

0 commit comments

Comments
 (0)