Skip to content

Commit cb00f8b

Browse files
authored
Merge pull request #4362 from tamasvajk/feature/sign-analysis-cleanup
Sign analysis cleanup
2 parents 06f1c89 + 94dc11c commit cb00f8b

File tree

7 files changed

+680
-277
lines changed

7 files changed

+680
-277
lines changed

csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/Sign.qll

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,25 @@ newtype TSign =
33
TZero() or
44
TPos()
55

6+
newtype TUnarySignOperation =
7+
TNegOp() or
8+
TIncOp() or
9+
TDecOp() or
10+
TBitNotOp()
11+
12+
newtype TBinarySignOperation =
13+
TAddOp() or
14+
TSubOp() or
15+
TMulOp() or
16+
TDivOp() or
17+
TRemOp() or
18+
TBitAndOp() or
19+
TBitOrOp() or
20+
TBitXorOp() or
21+
TLShiftOp() or
22+
TRShiftOp() or
23+
TURShiftOp()
24+
625
/** Class representing expression signs (+, -, 0). */
726
class Sign extends TSign {
827
/** Gets the string representation of this sign. */
@@ -67,6 +86,12 @@ class Sign extends TSign {
6786
this = TNeg() and s = TPos()
6887
}
6988

89+
/**
90+
* Gets a possible sign after subtracting an expression with sign `s` from an expression
91+
* that has this sign.
92+
*/
93+
Sign sub(Sign s) { result = add(s.neg()) }
94+
7095
/**
7196
* Gets a possible sign after multiplying an expression with sign `s` to an expression
7297
* that has this sign.
@@ -216,4 +241,40 @@ class Sign extends TSign {
216241
or
217242
result != TNeg() and this = TPos() and s != TZero()
218243
}
244+
245+
/** Perform `op` on this sign. */
246+
Sign applyUnaryOp(TUnarySignOperation op) {
247+
op = TIncOp() and result = inc()
248+
or
249+
op = TDecOp() and result = dec()
250+
or
251+
op = TNegOp() and result = neg()
252+
or
253+
op = TBitNotOp() and result = bitnot()
254+
}
255+
256+
/** Perform `op` on this sign and sign `s`. */
257+
Sign applyBinaryOp(Sign s, TBinarySignOperation op) {
258+
op = TAddOp() and result = add(s)
259+
or
260+
op = TSubOp() and result = sub(s)
261+
or
262+
op = TMulOp() and result = mul(s)
263+
or
264+
op = TDivOp() and result = div(s)
265+
or
266+
op = TRemOp() and result = rem(s)
267+
or
268+
op = TBitAndOp() and result = bitand(s)
269+
or
270+
op = TBitOrOp() and result = bitor(s)
271+
or
272+
op = TBitXorOp() and result = bitxor(s)
273+
or
274+
op = TLShiftOp() and result = lshift(s)
275+
or
276+
op = TRShiftOp() and result = rshift(s)
277+
or
278+
op = TURShiftOp() and result = urshift(s)
279+
}
219280
}

csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ private import SsaReadPositionCommon
1111
private import Sign
1212

1313
/** Gets the sign of `e` if this can be directly determined. */
14-
Sign certainExprSign(Expr e) {
14+
private Sign certainExprSign(Expr e) {
1515
exists(int i | e.(ConstantIntegerExpr).getIntValue() = i |
1616
i < 0 and result = TNeg()
1717
or
@@ -42,7 +42,7 @@ Sign certainExprSign(Expr e) {
4242
}
4343

4444
/** Holds if the sign of `e` is too complicated to determine. */
45-
predicate unknownSign(Expr e) {
45+
private predicate unknownSign(Expr e) {
4646
not exists(certainExprSign(e)) and
4747
(
4848
exists(IntegerLiteral lit | lit = e and not exists(lit.getValue().toInt()))
@@ -55,7 +55,7 @@ predicate unknownSign(Expr e) {
5555
not fromtyp instanceof NumericOrCharType
5656
)
5757
or
58-
unknownIntegerAccess(e)
58+
numericExprWithUnknownSign(e)
5959
)
6060
}
6161

@@ -185,29 +185,32 @@ private predicate hasGuard(SsaVariable v, SsaReadPosition pos, Sign s) {
185185
s = TZero() and zeroBound(_, v, pos)
186186
}
187187

188+
/**
189+
* Gets a possible sign of `v` at `pos` based on its definition, where the sign
190+
* might be ruled out by a guard.
191+
*/
188192
pragma[noinline]
189193
private Sign guardedSsaSign(SsaVariable v, SsaReadPosition pos) {
190-
// SSA variable can have sign `result`
191194
result = ssaDefSign(v) and
192195
pos.hasReadOfVar(v) and
193-
// there are guards at this position on `v` that might restrict it to be sign `result`.
194-
// (So we need to check if they are satisfied)
195196
hasGuard(v, pos, result)
196197
}
197198

199+
/**
200+
* Gets a possible sign of `v` at `pos` based on its definition, where no guard
201+
* can rule it out.
202+
*/
198203
pragma[noinline]
199204
private Sign unguardedSsaSign(SsaVariable v, SsaReadPosition pos) {
200-
// SSA variable can have sign `result`
201205
result = ssaDefSign(v) and
202206
pos.hasReadOfVar(v) and
203-
// there's no guard at this position on `v` that might restrict it to be sign `result`.
204207
not hasGuard(v, pos, result)
205208
}
206209

207210
/**
208-
* Gets the sign of `v` at read position `pos`, when there's at least one guard
209-
* on `v` at position `pos`. Each bound corresponding to a given sign must be met
210-
* in order for `v` to be of that sign.
211+
* Gets a possible sign of `v` at read position `pos`, where a guard could have
212+
* ruled out the sign but does not.
213+
* This does not check that the definition of `v` also allows the sign.
211214
*/
212215
private Sign guardedSsaSignOk(SsaVariable v, SsaReadPosition pos) {
213216
result = TPos() and
@@ -221,7 +224,7 @@ private Sign guardedSsaSignOk(SsaVariable v, SsaReadPosition pos) {
221224
}
222225

223226
/** Gets a possible sign for `v` at `pos`. */
224-
Sign ssaSign(SsaVariable v, SsaReadPosition pos) {
227+
private Sign ssaSign(SsaVariable v, SsaReadPosition pos) {
225228
result = unguardedSsaSign(v, pos)
226229
or
227230
result = guardedSsaSign(v, pos) and
@@ -230,7 +233,7 @@ Sign ssaSign(SsaVariable v, SsaReadPosition pos) {
230233

231234
/** Gets a possible sign for `v`. */
232235
pragma[nomagic]
233-
Sign ssaDefSign(SsaVariable v) {
236+
private Sign ssaDefSign(SsaVariable v) {
234237
result = explicitSsaDefSign(v)
235238
or
236239
result = implicitSsaDefSign(v)
@@ -242,6 +245,40 @@ Sign ssaDefSign(SsaVariable v) {
242245
)
243246
}
244247

248+
/** Returns the sign of explicit SSA definition `v`. */
249+
private Sign explicitSsaDefSign(SsaVariable v) {
250+
exists(VariableUpdate def | def = getExplicitSsaAssignment(v) |
251+
result = exprSign(getExprFromSsaAssignment(def))
252+
or
253+
anySign(result) and explicitSsaDefWithAnySign(def)
254+
or
255+
result = exprSign(getIncrementOperand(def)).inc()
256+
or
257+
result = exprSign(getDecrementOperand(def)).dec()
258+
)
259+
}
260+
261+
/** Returns the sign of implicit SSA definition `v`. */
262+
private Sign implicitSsaDefSign(SsaVariable v) {
263+
result = fieldSign(getImplicitSsaDeclaration(v))
264+
or
265+
anySign(result) and nonFieldImplicitSsaDefinition(v)
266+
}
267+
268+
/** Gets a possible sign for `f`. */
269+
private Sign fieldSign(Field f) {
270+
if not fieldWithUnknownSign(f)
271+
then
272+
result = exprSign(getAssignedValueToField(f))
273+
or
274+
fieldIncrementOperationOperand(f) and result = fieldSign(f).inc()
275+
or
276+
fieldDecrementOperationOperand(f) and result = fieldSign(f).dec()
277+
or
278+
result = specificFieldSign(f)
279+
else anySign(result)
280+
}
281+
245282
/** Gets a possible sign for `e`. */
246283
cached
247284
Sign exprSign(Expr e) {
@@ -250,18 +287,23 @@ Sign exprSign(Expr e) {
250287
or
251288
not exists(certainExprSign(e)) and
252289
(
253-
unknownSign(e)
290+
anySign(s) and unknownSign(e)
254291
or
255-
exists(SsaVariable v | getARead(v) = e | s = ssaVariableSign(v, e))
292+
exists(SsaVariable v | getARead(v) = e |
293+
s = ssaSign(v, any(SsaReadPositionBlock bb | getAnExpression(bb) = e))
294+
or
295+
not exists(SsaReadPositionBlock bb | getAnExpression(bb) = e) and
296+
s = ssaDefSign(v)
297+
)
256298
or
257-
e =
258-
any(VarAccess access |
259-
not exists(SsaVariable v | getARead(v) = access) and
260-
(
261-
s = fieldSign(getField(access.(FieldAccess))) or
262-
not access instanceof FieldAccess
263-
)
299+
exists(VarAccess access | access = e |
300+
not exists(SsaVariable v | getARead(v) = access) and
301+
(
302+
s = fieldSign(getField(access.(FieldAccess)))
303+
or
304+
anySign(s) and not access instanceof FieldAccess
264305
)
306+
)
265307
or
266308
s = specificSubExprSign(e)
267309
)
@@ -272,6 +314,41 @@ Sign exprSign(Expr e) {
272314
)
273315
}
274316

317+
/** Gets a possible sign for `e` from the signs of its child nodes. */
318+
private Sign specificSubExprSign(Expr e) {
319+
result = exprSign(getASubExprWithSameSign(e))
320+
or
321+
exists(DivExpr div | div = e |
322+
result = exprSign(div.getLeftOperand()) and
323+
result != TZero() and
324+
div.getRightOperand().(RealLiteral).getValue().toFloat() = 0
325+
)
326+
or
327+
exists(UnaryOperation unary | unary = e |
328+
result = exprSign(unary.getOperand()).applyUnaryOp(unary.getOp())
329+
)
330+
or
331+
exists(Sign s1, Sign s2 | binaryOpSigns(e, s1, s2) |
332+
result = s1.applyBinaryOp(s2, e.(BinaryOperation).getOp())
333+
)
334+
}
335+
336+
pragma[noinline]
337+
private predicate binaryOpSigns(Expr e, Sign lhs, Sign rhs) {
338+
lhs = binaryOpLhsSign(e) and
339+
rhs = binaryOpRhsSign(e)
340+
}
341+
342+
private Sign binaryOpLhsSign(BinaryOperation e) { result = exprSign(e.getLeftOperand()) }
343+
344+
private Sign binaryOpRhsSign(BinaryOperation e) { result = exprSign(e.getRightOperand()) }
345+
346+
/**
347+
* Dummy predicate that holds for any sign. This is added to improve readability
348+
* of cases where the sign is unrestricted.
349+
*/
350+
predicate anySign(Sign s) { any() }
351+
275352
/** Holds if `e` can be positive and cannot be negative. */
276353
predicate positive(Expr e) {
277354
exprSign(e) = TPos() and

0 commit comments

Comments
 (0)