Skip to content

Commit b6b7272

Browse files
committed
C++: SimpleRangeAnalysis for MulExpr by constant
1 parent a7d9715 commit b6b7272

File tree

4 files changed

+142
-53
lines changed

4 files changed

+142
-53
lines changed

cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll

Lines changed: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,65 @@ float safeFloor(float v) {
156156
result = v
157157
}
158158

159+
/** A `MulExpr` where exactly one operand is constant. */
160+
private class MulByConstantExpr extends MulExpr {
161+
float constant;
162+
Expr operand;
163+
164+
MulByConstantExpr() {
165+
exists(Expr constantExpr |
166+
this.hasOperands(constantExpr, operand) and
167+
constant = constantExpr.getFullyConverted().getValue().toFloat() and
168+
not exists(operand.getFullyConverted().getValue().toFloat())
169+
)
170+
}
171+
172+
/** Gets the value of the constant operand. */
173+
float getConstant() { result = constant }
174+
175+
/** Gets the non-constant operand. */
176+
Expr getOperand() { result = operand }
177+
}
178+
159179
private class UnsignedMulExpr extends MulExpr {
160-
UnsignedMulExpr() { this.getType().(IntegralType).isUnsigned() }
180+
UnsignedMulExpr() {
181+
this.getType().(IntegralType).isUnsigned() and
182+
// Avoid overlap. It should be slightly cheaper to analyze
183+
// `MulByConstantExpr`.
184+
not this instanceof MulByConstantExpr
185+
}
186+
}
187+
188+
/**
189+
* Holds if `expr` is effectively a multiplication of `operand` with the
190+
* positive constant `positive`.
191+
*/
192+
private predicate multipliesByPositive(Expr expr, Expr operand, float positive) {
193+
operand = expr.(MulByConstantExpr).getOperand() and
194+
positive = expr.(MulByConstantExpr).getConstant() and
195+
positive >= 0.0 // includes positive zero
196+
or
197+
operand = expr.(UnaryPlusExpr).getOperand() and
198+
positive = 1.0
199+
or
200+
operand = expr.(CommaExpr).getRightOperand() and
201+
positive = 1.0
202+
or
203+
operand = expr.(StmtExpr).getResultExpr() and
204+
positive = 1.0
205+
}
206+
207+
/**
208+
* Holds if `expr` is effectively a multiplication of `operand` with the
209+
* negative constant `negative`.
210+
*/
211+
private predicate multipliesByNegative(Expr expr, Expr operand, float negative) {
212+
operand = expr.(MulByConstantExpr).getOperand() and
213+
negative = expr.(MulByConstantExpr).getConstant() and
214+
negative < 0.0 // includes negative zero
215+
or
216+
operand = expr.(UnaryMinusExpr).getOperand() and
217+
negative = -1.0
161218
}
162219

163220
private class UnsignedAssignMulExpr extends AssignMulExpr {
@@ -172,9 +229,9 @@ private predicate analyzableExpr(Expr e) {
172229
(
173230
exists(getValue(e).toFloat())
174231
or
175-
e instanceof UnaryPlusExpr
232+
multipliesByPositive(e, _, _)
176233
or
177-
e instanceof UnaryMinusExpr
234+
multipliesByNegative(e, _, _)
178235
or
179236
e instanceof MinExpr
180237
or
@@ -200,10 +257,6 @@ private predicate analyzableExpr(Expr e) {
200257
or
201258
e instanceof RemExpr
202259
or
203-
e instanceof CommaExpr
204-
or
205-
e instanceof StmtExpr
206-
or
207260
// A conversion is analyzable, provided that its child has an arithmetic
208261
// type. (Sometimes the child is a reference type, and so does not get
209262
// any bounds.) Rather than checking whether the type of the child is
@@ -272,12 +325,14 @@ private predicate defDependsOnDef(
272325
* the structure of `getLowerBoundsImpl` and `getUpperBoundsImpl`.
273326
*/
274327
private predicate exprDependsOnDef(Expr e, RangeSsaDefinition srcDef, StackVariable srcVar) {
275-
exists(UnaryMinusExpr negateExpr | e = negateExpr |
276-
exprDependsOnDef(negateExpr.getOperand(), srcDef, srcVar)
328+
exists(Expr operand |
329+
multipliesByNegative(e, operand, _) and
330+
exprDependsOnDef(operand, srcDef, srcVar)
277331
)
278332
or
279-
exists(UnaryPlusExpr plusExpr | e = plusExpr |
280-
exprDependsOnDef(plusExpr.getOperand(), srcDef, srcVar)
333+
exists(Expr operand |
334+
multipliesByPositive(e, operand, _) and
335+
exprDependsOnDef(operand, srcDef, srcVar)
281336
)
282337
or
283338
exists(MinExpr minExpr | e = minExpr | exprDependsOnDef(minExpr.getAnOperand(), srcDef, srcVar))
@@ -316,14 +371,6 @@ private predicate exprDependsOnDef(Expr e, RangeSsaDefinition srcDef, StackVaria
316371
or
317372
exists(RemExpr remExpr | e = remExpr | exprDependsOnDef(remExpr.getAnOperand(), srcDef, srcVar))
318373
or
319-
exists(CommaExpr commaExpr | e = commaExpr |
320-
exprDependsOnDef(commaExpr.getRightOperand(), srcDef, srcVar)
321-
)
322-
or
323-
exists(StmtExpr stmtExpr | e = stmtExpr |
324-
exprDependsOnDef(stmtExpr.getResultExpr(), srcDef, srcVar)
325-
)
326-
or
327374
exists(Conversion convExpr | e = convExpr | exprDependsOnDef(convExpr.getExpr(), srcDef, srcVar))
328375
or
329376
// unsigned `&`
@@ -592,15 +639,16 @@ deprecated predicate positive_overflow(Expr expr) { exprMightOverflowPositively(
592639

593640
/** Only to be called by `getTruncatedLowerBounds`. */
594641
private float getLowerBoundsImpl(Expr expr) {
595-
exists(UnaryPlusExpr plusExpr |
596-
expr = plusExpr and
597-
result = getFullyConvertedLowerBounds(plusExpr.getOperand())
642+
exists(Expr operand, float operandLow, float positive |
643+
multipliesByPositive(expr, operand, positive) and
644+
operandLow = getFullyConvertedLowerBounds(operand) and
645+
result = positive * operandLow
598646
)
599647
or
600-
exists(UnaryMinusExpr negateExpr, float xHigh |
601-
expr = negateExpr and
602-
xHigh = getFullyConvertedUpperBounds(negateExpr.getOperand()) and
603-
result = -xHigh
648+
exists(Expr operand, float operandHigh, float negative |
649+
multipliesByNegative(expr, operand, negative) and
650+
operandHigh = getFullyConvertedUpperBounds(operand) and
651+
result = negative * operandHigh
604652
)
605653
or
606654
exists(MinExpr minExpr |
@@ -732,16 +780,6 @@ private float getLowerBoundsImpl(Expr expr) {
732780
)
733781
)
734782
or
735-
exists(CommaExpr commaExpr |
736-
expr = commaExpr and
737-
result = getFullyConvertedLowerBounds(commaExpr.getRightOperand())
738-
)
739-
or
740-
exists(StmtExpr stmtExpr |
741-
expr = stmtExpr and
742-
result = getFullyConvertedLowerBounds(stmtExpr.getResultExpr())
743-
)
744-
or
745783
// If the conversion is to an arithmetic type then we just return the
746784
// lower bound of the child. We do not need to handle truncation and
747785
// overflow here, because that is done in `getTruncatedLowerBounds`.
@@ -775,15 +813,16 @@ private float getLowerBoundsImpl(Expr expr) {
775813

776814
/** Only to be called by `getTruncatedUpperBounds`. */
777815
private float getUpperBoundsImpl(Expr expr) {
778-
exists(UnaryPlusExpr plusExpr |
779-
expr = plusExpr and
780-
result = getFullyConvertedUpperBounds(plusExpr.getOperand())
816+
exists(Expr operand, float operandHigh, float positive |
817+
multipliesByPositive(expr, operand, positive) and
818+
operandHigh = getFullyConvertedUpperBounds(operand) and
819+
result = positive * operandHigh
781820
)
782821
or
783-
exists(UnaryMinusExpr negateExpr, float xLow |
784-
expr = negateExpr and
785-
xLow = getFullyConvertedLowerBounds(negateExpr.getOperand()) and
786-
result = -xLow
822+
exists(Expr operand, float operandLow, float negative |
823+
multipliesByNegative(expr, operand, negative) and
824+
operandLow = getFullyConvertedLowerBounds(operand) and
825+
result = negative * operandLow
787826
)
788827
or
789828
exists(MaxExpr maxExpr |
@@ -913,16 +952,6 @@ private float getUpperBoundsImpl(Expr expr) {
913952
)
914953
)
915954
or
916-
exists(CommaExpr commaExpr |
917-
expr = commaExpr and
918-
result = getFullyConvertedUpperBounds(commaExpr.getRightOperand())
919-
)
920-
or
921-
exists(StmtExpr stmtExpr |
922-
expr = stmtExpr and
923-
result = getFullyConvertedUpperBounds(stmtExpr.getResultExpr())
924-
)
925-
or
926955
// If the conversion is to an arithmetic type then we just return the
927956
// upper bound of the child. We do not need to handle truncation and
928957
// overflow here, because that is done in `getTruncatedUpperBounds`.

cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/lowerBound.expected

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,25 @@
510510
| test.c:504:3:504:9 | ulconst | 10 |
511511
| test.c:505:10:505:16 | uiconst | 40 |
512512
| test.c:505:20:505:26 | ulconst | 40 |
513+
| test.c:509:7:509:7 | i | -2147483648 |
514+
| test.c:509:18:509:18 | i | -1 |
515+
| test.c:510:5:510:5 | i | -2147483648 |
516+
| test.c:510:13:510:13 | i | -1 |
517+
| test.c:511:9:511:9 | i | -5 |
518+
| test.c:513:5:513:5 | i | -2147483648 |
519+
| test.c:513:9:513:9 | i | -5 |
520+
| test.c:514:9:514:9 | i | -30 |
521+
| test.c:516:5:516:5 | i | -30 |
522+
| test.c:517:9:517:9 | i | -2147483648 |
523+
| test.c:519:5:519:5 | i | -2147483648 |
524+
| test.c:520:9:520:9 | i | -2147483648 |
525+
| test.c:522:7:522:7 | i | -2147483648 |
526+
| test.c:523:5:523:5 | i | -2147483648 |
527+
| test.c:523:9:523:9 | i | -1 |
528+
| test.c:524:9:524:9 | i | 1 |
529+
| test.c:526:3:526:3 | i | -2147483648 |
530+
| test.c:526:7:526:7 | i | -2147483648 |
531+
| test.c:527:10:527:10 | i | -2147483648 |
513532
| test.cpp:10:7:10:7 | b | -2147483648 |
514533
| test.cpp:11:5:11:5 | x | -2147483648 |
515534
| test.cpp:13:10:13:10 | x | -2147483648 |

cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,3 +504,25 @@ unsigned long mul_assign(unsigned int ui) {
504504
ulconst *= 4;
505505
return uiconst + ulconst; // 40 .. 40 for both
506506
}
507+
508+
int mul_by_constant(int i, int j) {
509+
if (i >= -1 && i <= 2) {
510+
i = 5 * i;
511+
out(i); // -5 .. 10
512+
513+
i = i * -3;
514+
out(i); // -30 .. 15
515+
516+
i *= 7;
517+
out(i); // -210 .. 105 [BUG: not supported]
518+
519+
i *= -11;
520+
out(i); // -1155 .. 2310 [BUG: not supported]
521+
}
522+
if (i == -1) {
523+
i = i * (int)0xffFFffFF; // fully converted literal is -1
524+
out(i); // 1 .. 1
525+
}
526+
i = i * -1;
527+
return i; // -2^31 .. 2^31-1
528+
}

cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,25 @@
510510
| test.c:504:3:504:9 | ulconst | 10 |
511511
| test.c:505:10:505:16 | uiconst | 40 |
512512
| test.c:505:20:505:26 | ulconst | 40 |
513+
| test.c:509:7:509:7 | i | 2147483647 |
514+
| test.c:509:18:509:18 | i | 2147483647 |
515+
| test.c:510:5:510:5 | i | 2147483647 |
516+
| test.c:510:13:510:13 | i | 2 |
517+
| test.c:511:9:511:9 | i | 10 |
518+
| test.c:513:5:513:5 | i | 2147483647 |
519+
| test.c:513:9:513:9 | i | 10 |
520+
| test.c:514:9:514:9 | i | 15 |
521+
| test.c:516:5:516:5 | i | 15 |
522+
| test.c:517:9:517:9 | i | 2147483647 |
523+
| test.c:519:5:519:5 | i | 2147483647 |
524+
| test.c:520:9:520:9 | i | 2147483647 |
525+
| test.c:522:7:522:7 | i | 2147483647 |
526+
| test.c:523:5:523:5 | i | 2147483647 |
527+
| test.c:523:9:523:9 | i | -1 |
528+
| test.c:524:9:524:9 | i | 1 |
529+
| test.c:526:3:526:3 | i | 2147483647 |
530+
| test.c:526:7:526:7 | i | 2147483647 |
531+
| test.c:527:10:527:10 | i | 2147483647 |
513532
| test.cpp:10:7:10:7 | b | 2147483647 |
514533
| test.cpp:11:5:11:5 | x | 2147483647 |
515534
| test.cpp:13:10:13:10 | x | 2147483647 |

0 commit comments

Comments
 (0)