Skip to content

Commit 4b4b8a9

Browse files
authored
Merge pull request #4074 from jbj/SimpleRangeAnalysis-extensible
C++: extensible range analysis
2 parents c917cd0 + f7273b8 commit 4b4b8a9

File tree

5 files changed

+177
-16
lines changed

5 files changed

+177
-16
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* EXPERIMENTAL: The API of this module may change without notice.
3+
*
4+
* Provides a class for modeling `Expr`s with a restricted range.
5+
*/
6+
7+
import cpp
8+
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
9+
10+
/**
11+
* EXPERIMENTAL: The API of this class may change without notice.
12+
*
13+
* An expression for which a range can be deduced. Extend this class to add
14+
* functionality to the range analysis library.
15+
*/
16+
abstract class SimpleRangeAnalysisExpr extends Expr {
17+
/**
18+
* Gets the lower bound of the expression.
19+
*
20+
* Implementations of this predicate should use
21+
* `getFullyConvertedLowerBounds` and `getFullyConvertedUpperBounds` for
22+
* recursive calls to get the bounds of their children.
23+
*/
24+
abstract float getLowerBounds();
25+
26+
/**
27+
* Gets the upper bound of the expression.
28+
*
29+
* Implementations of this predicate should use
30+
* `getFullyConvertedLowerBounds` and `getFullyConvertedUpperBounds` for
31+
* recursive calls to get the bounds of their children.
32+
*/
33+
abstract float getUpperBounds();
34+
35+
/**
36+
* Holds if the range this expression depends on the definition `srcDef` for
37+
* StackVariable `srcVar`.
38+
*
39+
* Because this predicate cannot be recursive, most implementations should
40+
* override `dependsOnChild` instead.
41+
*/
42+
predicate dependsOnDef(RangeSsaDefinition srcDef, StackVariable srcVar) { none() }
43+
44+
/**
45+
* Holds if this expression depends on the range of its unconverted
46+
* subexpression `child`. This information is used to inform the range
47+
* analysis about cyclic dependencies. Without this information, range
48+
* analysis might work for simple cases but will go into infinite loops on
49+
* complex code.
50+
*
51+
* For example, when modeling a function call whose return value depends on
52+
* all of its arguments, implement this predicate as
53+
* `child = this.getAnArgument()`.
54+
*/
55+
abstract predicate dependsOnChild(Expr child);
56+
}
57+
58+
import SimpleRangeAnalysisInternal
59+
60+
/**
61+
* This class exists to prevent the QL front end from emitting compile errors
62+
* inside `SimpleRangeAnalysis.qll` about certain conjuncts being empty
63+
* because the overrides of `SimpleRangeAnalysisExpr` that happen to be in
64+
* scope do not make use of every feature it offers.
65+
*/
66+
private class Empty extends SimpleRangeAnalysisExpr {
67+
Empty() {
68+
// This predicate is complicated enough that the QL type checker doesn't
69+
// see it as empty but simple enough that the optimizer should.
70+
this = this and none()
71+
}
72+
73+
override float getLowerBounds() { none() }
74+
75+
override float getUpperBounds() { none() }
76+
77+
override predicate dependsOnChild(Expr child) { none() }
78+
}

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

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
import cpp
4646
private import RangeAnalysisUtils
47+
private import experimental.semmle.code.cpp.models.interfaces.SimpleRangeAnalysisExpr
4748
import RangeSSA
4849
import SimpleRangeAnalysisCached
4950
private import NanAnalysis
@@ -213,6 +214,9 @@ private predicate analyzableExpr(Expr e) {
213214
or
214215
// `>>` by a constant
215216
exists(e.(RShiftExpr).getRightOperand().getValue())
217+
or
218+
// A modeled expression for range analysis
219+
e instanceof SimpleRangeAnalysisExpr
216220
)
217221
}
218222

@@ -328,6 +332,16 @@ private predicate exprDependsOnDef(Expr e, RangeSsaDefinition srcDef, StackVaria
328332
)
329333
or
330334
e = srcDef.getAUse(srcVar)
335+
or
336+
// A modeled expression for range analysis
337+
exists(SimpleRangeAnalysisExpr rae | rae = e |
338+
rae.dependsOnDef(srcDef, srcVar)
339+
or
340+
exists(Expr child |
341+
rae.dependsOnChild(child) and
342+
exprDependsOnDef(child, srcDef, srcVar)
343+
)
344+
)
331345
}
332346

333347
/**
@@ -445,13 +459,6 @@ private float addRoundingDownSmall(float x, float small) {
445459
if (x + small) - x > small then result = (x + small).nextDown() else result = (x + small)
446460
}
447461

448-
/**
449-
* Gets the truncated lower bounds of the fully converted expression.
450-
*/
451-
private float getFullyConvertedLowerBounds(Expr expr) {
452-
result = getTruncatedLowerBounds(expr.getFullyConverted())
453-
}
454-
455462
/**
456463
* Gets the lower bounds of the expression.
457464
*
@@ -498,13 +505,6 @@ private float getTruncatedLowerBounds(Expr expr) {
498505
result = exprMinVal(expr)
499506
}
500507

501-
/**
502-
* Gets the truncated upper bounds of the fully converted expression.
503-
*/
504-
private float getFullyConvertedUpperBounds(Expr expr) {
505-
result = getTruncatedUpperBounds(expr.getFullyConverted())
506-
}
507-
508508
/**
509509
* Gets the upper bounds of the expression.
510510
*
@@ -728,7 +728,9 @@ private float getLowerBoundsImpl(Expr expr) {
728728
or
729729
// Use SSA to get the lower bounds for a variable use.
730730
exists(RangeSsaDefinition def, StackVariable v | expr = def.getAUse(v) |
731-
result = getDefLowerBounds(def, v)
731+
result = getDefLowerBounds(def, v) and
732+
// Not explicitly modeled by a SimpleRangeAnalysisExpr
733+
not expr instanceof SimpleRangeAnalysisExpr
732734
)
733735
or
734736
// unsigned `&` (tighter bounds may exist)
@@ -744,6 +746,12 @@ private float getLowerBoundsImpl(Expr expr) {
744746
right = rsExpr.getRightOperand().getFullyConverted().getValue().toInt() and
745747
result = safeFloor(left / 2.pow(right))
746748
)
749+
or
750+
// A modeled expression for range analysis
751+
exists(SimpleRangeAnalysisExpr rangeAnalysisExpr |
752+
rangeAnalysisExpr = expr and
753+
result = rangeAnalysisExpr.getLowerBounds()
754+
)
747755
}
748756

749757
/** Only to be called by `getTruncatedUpperBounds`. */
@@ -902,7 +910,9 @@ private float getUpperBoundsImpl(Expr expr) {
902910
or
903911
// Use SSA to get the upper bounds for a variable use.
904912
exists(RangeSsaDefinition def, StackVariable v | expr = def.getAUse(v) |
905-
result = getDefUpperBounds(def, v)
913+
result = getDefUpperBounds(def, v) and
914+
// Not explicitly modeled by a SimpleRangeAnalysisExpr
915+
not expr instanceof SimpleRangeAnalysisExpr
906916
)
907917
or
908918
// unsigned `&` (tighter bounds may exist)
@@ -920,6 +930,12 @@ private float getUpperBoundsImpl(Expr expr) {
920930
right = rsExpr.getRightOperand().getFullyConverted().getValue().toInt() and
921931
result = safeFloor(left / 2.pow(right))
922932
)
933+
or
934+
// A modeled expression for range analysis
935+
exists(SimpleRangeAnalysisExpr rangeAnalysisExpr |
936+
rangeAnalysisExpr = expr and
937+
result = rangeAnalysisExpr.getUpperBounds()
938+
)
923939
}
924940

925941
/**
@@ -1504,3 +1520,25 @@ private module SimpleRangeAnalysisCached {
15041520
convertedExprMightOverflowPositively(expr)
15051521
}
15061522
}
1523+
1524+
/**
1525+
* INTERNAL: do not use. This module contains utilities for use in the
1526+
* experimental `SimpleRangeAnalysisExpr` module.
1527+
*/
1528+
module SimpleRangeAnalysisInternal {
1529+
/**
1530+
* Gets the truncated lower bounds of the fully converted expression.
1531+
*/
1532+
float getFullyConvertedLowerBounds(Expr expr) {
1533+
result = getTruncatedLowerBounds(expr.getFullyConverted())
1534+
}
1535+
1536+
/**
1537+
* Gets the truncated upper bounds of the fully converted expression.
1538+
*/
1539+
float getFullyConvertedUpperBounds(Expr expr) {
1540+
result = getTruncatedUpperBounds(expr.getFullyConverted())
1541+
}
1542+
}
1543+
1544+
private import SimpleRangeAnalysisInternal
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// Adds its arguments (has custom modeling in QL)
2+
int custom_add_function(int a, int b);
3+
4+
int test_extensibility_add(int x) {
5+
if (x >= -10 && x <= 10) {
6+
int result = custom_add_function(x, 100);
7+
return result; // 90 .. 110
8+
}
9+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| extensibility.c:5:7:5:7 | x | -2.147483648E9 | 2.147483647E9 |
2+
| extensibility.c:5:19:5:19 | x | -10.0 | 2.147483647E9 |
3+
| extensibility.c:6:38:6:38 | x | -10.0 | 10.0 |
4+
| extensibility.c:7:12:7:17 | result | 90.0 | 110.0 |
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
2+
import experimental.semmle.code.cpp.models.interfaces.SimpleRangeAnalysisExpr
3+
4+
class CustomAddFunctionCall extends SimpleRangeAnalysisExpr, FunctionCall {
5+
CustomAddFunctionCall() { this.getTarget().hasGlobalName("custom_add_function") }
6+
7+
override float getLowerBounds() {
8+
exists(float lower0, float lower1 |
9+
lower0 = getFullyConvertedLowerBounds(this.getArgument(0)) and
10+
lower1 = getFullyConvertedLowerBounds(this.getArgument(1)) and
11+
// Note: this rounds toward 0, not -Inf as it should
12+
result = lower0 + lower1
13+
)
14+
}
15+
16+
override float getUpperBounds() {
17+
exists(float upper0, float upper1 |
18+
upper0 = getFullyConvertedUpperBounds(this.getArgument(0)) and
19+
upper1 = getFullyConvertedUpperBounds(this.getArgument(1)) and
20+
// Note: this rounds toward 0, not Inf as it should
21+
result = upper0 + upper1
22+
)
23+
}
24+
25+
override predicate dependsOnChild(Expr child) { child = this.getAnArgument() }
26+
}
27+
28+
from VariableAccess expr, float lower, float upper
29+
where
30+
lower = lowerBound(expr) and
31+
upper = upperBound(expr)
32+
select expr, lower, upper

0 commit comments

Comments
 (0)