Skip to content

Commit 6440db7

Browse files
authored
Merge pull request #4420 from jbj/SimpleRangeAnalysis-widen-Expr
C++: SimpleRangeAnalysis: widen recursive *, +, -
2 parents 725194a + 9b12cea commit 6440db7

File tree

4 files changed

+73
-2
lines changed

4 files changed

+73
-2
lines changed

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

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,39 @@ private predicate isRecursiveDef(RangeSsaDefinition def, StackVariable v) {
460460
defDependsOnDefTransitively(def, v, def, v)
461461
}
462462

463+
/**
464+
* Holds if the bounds of `e` depend on a recursive definition, meaning that
465+
* `e` is likely to have many candidate bounds during the main recursion.
466+
*/
467+
private predicate isRecursiveExpr(Expr e) {
468+
exists(RangeSsaDefinition def, StackVariable v | exprDependsOnDef(e, def, v) |
469+
isRecursiveDef(def, v)
470+
)
471+
}
472+
473+
/**
474+
* Holds if `binop` is a binary operation that's likely to be assigned a
475+
* quadratic (or more) number of candidate bounds during the analysis. This can
476+
* happen when two conditions are satisfied:
477+
* 1. It is likely there are many more candidate bounds for `binop` than for
478+
* its operands. For example, the number of candidate bounds for `x + y`,
479+
* denoted here nbounds(`x + y`), will be O(nbounds(`x`) * nbounds(`y`)).
480+
* In contrast, nbounds(`b ? x : y`) is only O(nbounds(`x`) + nbounds(`y`)).
481+
* 2. Both operands of `binop` are recursively determined and are therefore
482+
* likely to have a large number of candidate bounds.
483+
*/
484+
private predicate isRecursiveBinary(BinaryOperation binop) {
485+
(
486+
binop instanceof UnsignedMulExpr
487+
or
488+
binop instanceof AddExpr
489+
or
490+
binop instanceof SubExpr
491+
) and
492+
isRecursiveExpr(binop.getLeftOperand()) and
493+
isRecursiveExpr(binop.getRightOperand())
494+
}
495+
463496
/**
464497
* We distinguish 3 kinds of RangeSsaDefinition:
465498
*
@@ -581,7 +614,16 @@ private float getTruncatedLowerBounds(Expr expr) {
581614
// overflow, so we replace invalid bounds with exprMinVal.
582615
exists(float newLB | newLB = normalizeFloatUp(getLowerBoundsImpl(expr)) |
583616
if exprMinVal(expr) <= newLB and newLB <= exprMaxVal(expr)
584-
then result = newLB
617+
then
618+
// Apply widening where we might get a combinatorial explosion.
619+
if isRecursiveBinary(expr)
620+
then
621+
result =
622+
max(float widenLB |
623+
widenLB = wideningLowerBounds(expr.getUnspecifiedType()) and
624+
not widenLB > newLB
625+
)
626+
else result = newLB
585627
else result = exprMinVal(expr)
586628
)
587629
or
@@ -628,7 +670,16 @@ private float getTruncatedUpperBounds(Expr expr) {
628670
// `exprMaxVal`.
629671
exists(float newUB | newUB = normalizeFloatUp(getUpperBoundsImpl(expr)) |
630672
if exprMinVal(expr) <= newUB and newUB <= exprMaxVal(expr)
631-
then result = newUB
673+
then
674+
// Apply widening where we might get a combinatorial explosion.
675+
if isRecursiveBinary(expr)
676+
then
677+
result =
678+
min(float widenUB |
679+
widenUB = wideningUpperBounds(expr.getUnspecifiedType()) and
680+
not widenUB < newUB
681+
)
682+
else result = newUB
632683
else result = exprMaxVal(expr)
633684
)
634685
or

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,12 @@
582582
| test.c:635:9:635:10 | ss | -32768 |
583583
| test.c:638:7:638:8 | ss | -32768 |
584584
| test.c:639:9:639:10 | ss | -1 |
585+
| test.c:645:8:645:8 | s | -2147483648 |
586+
| test.c:645:15:645:15 | s | 0 |
587+
| test.c:645:23:645:23 | s | 0 |
588+
| test.c:646:18:646:18 | s | 0 |
589+
| test.c:646:22:646:22 | s | 0 |
590+
| test.c:647:9:647:14 | result | 0 |
585591
| test.cpp:10:7:10:7 | b | -2147483648 |
586592
| test.cpp:11:5:11:5 | x | -2147483648 |
587593
| test.cpp:13:10:13:10 | x | -2147483648 |

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,4 +638,12 @@ void two_bounds_from_one_test(short ss, unsigned short us) {
638638
if (ss + 1 < sizeof(int)) {
639639
out(ss); // -1 .. 2
640640
}
641+
}
642+
643+
void widen_recursive_expr() {
644+
int s;
645+
for (s = 0; s < 10; s++) {
646+
int result = s + s; // 0 .. 9 [BUG: upper bound is 15 due to widening]
647+
out(result); // 0 .. 18 [BUG: upper bound is 127 due to double widening]
648+
}
641649
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,12 @@
582582
| test.c:635:9:635:10 | ss | 32767 |
583583
| test.c:638:7:638:8 | ss | 32767 |
584584
| test.c:639:9:639:10 | ss | 2 |
585+
| test.c:645:8:645:8 | s | 2147483647 |
586+
| test.c:645:15:645:15 | s | 127 |
587+
| test.c:645:23:645:23 | s | 15 |
588+
| test.c:646:18:646:18 | s | 15 |
589+
| test.c:646:22:646:22 | s | 15 |
590+
| test.c:647:9:647:14 | result | 127 |
585591
| test.cpp:10:7:10:7 | b | 2147483647 |
586592
| test.cpp:11:5:11:5 | x | 2147483647 |
587593
| test.cpp:13:10:13:10 | x | 2147483647 |

0 commit comments

Comments
 (0)