Skip to content

Commit f026d3a

Browse files
committed
C++: Improve bitwise and range analysis
1 parent 78625b7 commit f026d3a

File tree

4 files changed

+148
-4
lines changed

4 files changed

+148
-4
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
private import cpp
2+
3+
Expr getLOp(Operation o) {
4+
result = o.(BinaryOperation).getLeftOperand() or
5+
result = o.(Assignment).getLValue()
6+
}
7+
8+
Expr getROp(Operation o) {
9+
result = o.(BinaryOperation).getRightOperand() or
10+
result = o.(Assignment).getRValue()
11+
}
12+
13+
private newtype TBinaryOrAssignOperation =
14+
BinaryOp(BinaryOperation op) or
15+
AssignOp(AssignOperation op)
16+
17+
class BinaryOrAssignOperation extends TBinaryOrAssignOperation {
18+
BinaryOperation asBinaryOp() { this = BinaryOp(result) }
19+
20+
AssignOperation asAssignOp() { this = AssignOp(result) }
21+
22+
Expr getLeftOperand() { result = getLOp(asBinaryOp()) or result = getLOp(asAssignOp()) }
23+
24+
Expr getRightOperand() { result = getROp(asBinaryOp()) or result = getROp(asAssignOp()) }
25+
26+
Expr getAnOperand() { result = getLeftOperand() or result = getRightOperand() }
27+
28+
Operation getOperation() { result = asBinaryOp() or result = asAssignOp() }
29+
30+
string toString() { result = asBinaryOp().toString() or result = asAssignOp().toString() }
31+
}

cpp/ql/src/experimental/semmle/code/cpp/rangeanalysis/ExtendedRangeAnalysis.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
22
//
33
// Import each extension we want to enable
44
import extensions.SubtractSelf
5+
import extensions.ConstantBitwiseAndExprRange
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
private import cpp
2+
private import experimental.semmle.code.cpp.models.interfaces.SimpleRangeAnalysisExpr
3+
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
4+
private import experimental.semmle.code.cpp.rangeanalysis.BinaryOrAssignOperation
5+
6+
/**
7+
* The current implementation for `BitwiseAndExpr` only handles cases where both operands are
8+
* either unsigned or non-negative constants. This class not only covers these cases, but also
9+
* adds support for `&` expressions between a signed integer with a non-negative range and a
10+
* non-negative constant. It also adds support for `&=` for the same set of cases as `&`.
11+
*/
12+
private class ConstantBitwiseAndExprRange extends SimpleRangeAnalysisExpr {
13+
BinaryOrAssignConstantBitwiseAndExpr e;
14+
15+
ConstantBitwiseAndExprRange() { this = e.getOperation() }
16+
17+
BinaryOrAssignConstantBitwiseAndExpr getExpr() { result = e }
18+
19+
Expr getLeftOperand() { result = e.getLeftOperand() }
20+
21+
Expr getRightOperand() { result = e.getRightOperand() }
22+
23+
override float getLowerBounds() { result = e.getLowerBounds() }
24+
25+
override float getUpperBounds() { result = e.getUpperBounds() }
26+
27+
override predicate dependsOnChild(Expr child) { child = e.getAnOperand() }
28+
}
29+
30+
private class ConstantBitwiseAndExprOp extends Expr {
31+
BinaryOrAssignConstantBitwiseAndExpr b;
32+
float lowerBound;
33+
float upperBound;
34+
35+
ConstantBitwiseAndExprOp() {
36+
this = b.getAnOperand() and
37+
lowerBound = getFullyConvertedLowerBounds(this) and
38+
upperBound = getFullyConvertedUpperBounds(this) and
39+
lowerBound <= upperBound
40+
}
41+
42+
float getLowerBound() { result = lowerBound }
43+
44+
float getUpperBound() { result = upperBound }
45+
46+
predicate hasNegativeRange() { getLowerBound() < 0 or getUpperBound() < 0 }
47+
}
48+
49+
/**
50+
* Holds if `e` is a constant or if it is a variable with a constant value
51+
*/
52+
float evaluateConstantExpr(Expr e) {
53+
result = e.getValue().toFloat()
54+
or
55+
exists(SsaDefinition defn, StackVariable sv |
56+
defn.getAUse(sv) = e and
57+
result = defn.getDefiningValue(sv).getValue().toFloat()
58+
)
59+
}
60+
61+
private class BinaryOrAssignConstantBitwiseAndExpr extends BinaryOrAssignOperation {
62+
BinaryOrAssignConstantBitwiseAndExpr() {
63+
(
64+
getOperation() instanceof BitwiseAndExpr
65+
or
66+
getOperation() instanceof AssignAndExpr
67+
) and
68+
// Make sure all operands and the result type are integral
69+
getOperation().getUnspecifiedType() instanceof IntegralType and
70+
getLeftOperand().getUnspecifiedType() instanceof IntegralType and
71+
getRightOperand().getUnspecifiedType() instanceof IntegralType and
72+
// No operands can be negative constants
73+
not (evaluateConstantExpr(getLeftOperand()) < 0 or evaluateConstantExpr(getRightOperand()) < 0) and
74+
// At least one operand must be a non-negative constant
75+
(evaluateConstantExpr(getLeftOperand()) >= 0 or evaluateConstantExpr(getRightOperand()) >= 0)
76+
}
77+
78+
float getLowerBounds() {
79+
// If both operands have non-negative ranges, the lower bound is zero. If an operand can have
80+
// negative values, the lower bound is unconstrained.
81+
exists(ConstantBitwiseAndExprOp l, ConstantBitwiseAndExprOp r |
82+
l = getLeftOperand() and
83+
r = getRightOperand() and
84+
(
85+
(l.hasNegativeRange() or r.hasNegativeRange()) and
86+
result = exprMinVal(getOperation())
87+
or
88+
// This technically results in two lowerBounds when an operand range is negative, but
89+
// that's fine since `exprMinVal(x) <= 0`. We can't use an if statement here without
90+
// non-monotonic recursion issues
91+
result = 0
92+
)
93+
)
94+
}
95+
96+
float getUpperBounds() {
97+
// If an operand can have negative values, the upper bound is unconstrained.
98+
// Otherwise, the upper bound is the maximum of the upper bounds of the operands
99+
exists(ConstantBitwiseAndExprOp l, ConstantBitwiseAndExprOp r |
100+
l = getLeftOperand() and
101+
r = getRightOperand() and
102+
(
103+
(l.hasNegativeRange() or r.hasNegativeRange()) and
104+
result = exprMaxVal(getOperation())
105+
or
106+
// This technically results in two upperBounds when an operand range is negative, but
107+
// that's fine since `exprMaxVal(b) >= result`
108+
result = r.getUpperBound().minimum(l.getUpperBound())
109+
)
110+
)
111+
}
112+
}

cpp/ql/test/experimental/library-tests/rangeanalysis/bitwiseand/bitwiseand.expected

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
| bitwiseand.cpp:7:3:7:8 | ... &= ... | 0.0 | 255.0 |
2-
| bitwiseand.cpp:15:3:15:7 | ... & ... | -2.147483648E9 | 2.147483647E9 |
3-
| bitwiseand.cpp:16:3:16:7 | ... & ... | -2.147483648E9 | 2.147483647E9 |
4-
| bitwiseand.cpp:17:3:17:20 | ... & ... | -2.147483648E9 | 2.147483647E9 |
1+
| bitwiseand.cpp:7:3:7:8 | ... &= ... | 0.0 | 7.0 |
2+
| bitwiseand.cpp:15:3:15:7 | ... & ... | 0.0 | 0.0 |
3+
| bitwiseand.cpp:16:3:16:7 | ... & ... | 0.0 | 7.0 |
4+
| bitwiseand.cpp:17:3:17:20 | ... & ... | 0.0 | 7.0 |
55
| bitwiseand.cpp:21:3:21:16 | ... & ... | 0.0 | 255.0 |
66
| bitwiseand.cpp:28:5:28:9 | ... & ... | -2.147483648E9 | 2.147483647E9 |
77
| bitwiseand.cpp:32:5:32:9 | ... & ... | 0.0 | 100.0 |

0 commit comments

Comments
 (0)