Skip to content

Commit 051d36e

Browse files
Gulshan Singhgsingh93
authored andcommitted
Add ConstantLShiftExprRange and ConstantRShiftExprRange classes
1 parent 5710289 commit 051d36e

File tree

4 files changed

+308
-18
lines changed

4 files changed

+308
-18
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
private import cpp
2+
private import experimental.semmle.code.cpp.models.interfaces.SimpleRangeAnalysisExpr
3+
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
4+
5+
float evaluateConstantExpr2(Expr e) {
6+
result = e.getValue().toFloat()
7+
or
8+
// This handles when a constant value is put into a variable
9+
// and the variable is used later
10+
exists(SsaDefinition defn, StackVariable sv |
11+
defn.getAUse(sv) = e and
12+
result = defn.getDefiningValue(sv).getValue().toFloat()
13+
)
14+
}
15+
16+
// If the constant right operand is negative or is greater than or equal to the number of
17+
// bits in the left operands type, then the result is undefined (except on the IA-32
18+
// architecture where the shift value is masked with 0b00011111, but we can't
19+
// assume the architecture).
20+
bindingset[val]
21+
pragma[inline]
22+
private predicate isValidShiftExprShift(float val, Expr l) {
23+
val >= 0 and
24+
// We use getFullyConverted because the spec says to use the *promoted* left operand
25+
val < (l.getFullyConverted().getUnderlyingType().getSize() * 8)
26+
}
27+
28+
bindingset[val, shift, max_val]
29+
private predicate canLShiftOverflow(int val, int shift, int max_val) {
30+
// val << shift = val * 2^shift > max_val => val > max_val/2^shift = max_val >> b
31+
val > max_val.bitShiftRight(shift)
32+
}
33+
34+
/**
35+
* This handles the `<<` and `<<=` operators when at least one operand is a constant (and if the right operand
36+
* is a constant, it must be "valid" (see `isValidShiftExprShift`)). When handling any undefined behavior, it
37+
* leaves the values unconstrained. From the C++ standard: "The behavior is undefined if the right
38+
* operand is negative, or greater than or equal to the length in bits of the promoted left operand.
39+
* The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1
40+
* has an unsigned type, the value of the result is E1 × 2 E2, reduced modulo one more than the
41+
* maximum value representable in the result type. Otherwise, if E1 has a signed type and
42+
* non-negative value, and E1 × 2 E2 is representable in the corresponding unsigned type of the
43+
* result type, then that value, converted to the result type, is the resulting value; otherwise,
44+
* the behavior is undefined."
45+
*/
46+
class ConstantLShiftExprRange extends SimpleRangeAnalysisExpr {
47+
/**
48+
* Holds for `a << b` or `a <<= b` in one of the following two cases:
49+
* 1. `a` is a constant and `b` is not
50+
* 2. `b` is constant
51+
*
52+
* We don't handle the case where `a` and `b` are both non-constant values.
53+
*/
54+
ConstantLShiftExprRange() {
55+
getUnspecifiedType() instanceof IntegralType and
56+
exists(Expr l, Expr r |
57+
l = this.(LShiftExpr).getLeftOperand() and
58+
r = this.(LShiftExpr).getRightOperand()
59+
or
60+
l = this.(AssignLShiftExpr).getLValue() and
61+
r = this.(AssignLShiftExpr).getRValue()
62+
|
63+
l.getUnspecifiedType() instanceof IntegralType and
64+
r.getUnspecifiedType() instanceof IntegralType and
65+
(
66+
// If the left operand is a constant, verify that the right operand is not a constant
67+
exists(evaluateConstantExpr2(l)) and not exists(evaluateConstantExpr2(r))
68+
or
69+
// If the right operand is a constant, check if it is a valid shift expression
70+
exists(float constROp |
71+
constROp = evaluateConstantExpr2(r) and isValidShiftExprShift(constROp, l)
72+
)
73+
)
74+
)
75+
}
76+
77+
Expr getLeftOperand() {
78+
result = this.(LShiftExpr).getLeftOperand() or
79+
result = this.(AssignLShiftExpr).getLValue()
80+
}
81+
82+
Expr getRightOperand() {
83+
result = this.(LShiftExpr).getRightOperand() or
84+
result = this.(AssignLShiftExpr).getRValue()
85+
}
86+
87+
override float getLowerBounds() {
88+
exists(int lLower, int lUpper, int rLower, int rUpper |
89+
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
90+
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
91+
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
92+
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
93+
lLower <= lUpper and
94+
rLower <= rUpper
95+
|
96+
if
97+
lLower < 0
98+
or
99+
not (
100+
isValidShiftExprShift(rLower, getLeftOperand()) and
101+
isValidShiftExprShift(rUpper, getLeftOperand())
102+
)
103+
then
104+
// We don't want to deal with shifting negative numbers at the moment,
105+
// and a negative shift is undefined, so we set to the minimum value
106+
result = exprMinVal(this)
107+
else
108+
// If we have `0b01010000 << [0, 2]`, the max value for 8 bits is 0b10100000
109+
// (a shift of 1) but doing a shift by the upper bound would give 0b01000000.
110+
// So if the left shift operation causes an overflow, we just assume the max value
111+
// If necessary, we may be able to improve this bound in the future
112+
if canLShiftOverflow(lUpper, rUpper, exprMaxVal(this).(int))
113+
then result = exprMinVal(this)
114+
else result = lLower.bitShiftLeft(rLower)
115+
)
116+
}
117+
118+
override float getUpperBounds() {
119+
exists(int lLower, int lUpper, int rLower, int rUpper |
120+
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
121+
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
122+
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
123+
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
124+
lLower <= lUpper and
125+
rLower <= rUpper
126+
|
127+
if
128+
lLower < 0
129+
or
130+
not (
131+
isValidShiftExprShift(rLower, getLeftOperand()) and
132+
isValidShiftExprShift(rUpper, getLeftOperand())
133+
)
134+
then
135+
// We don't want to deal with shifting negative numbers at the moment,
136+
// and a negative shift is undefined, so we set it to the maximum value
137+
result = exprMaxVal(this)
138+
else
139+
// If we have `0b01010000 << [0, 2]`, the max value for 8 bits is 0b10100000
140+
// (a shift of 1) but doing a shift by the upper bound would give 0b01000000.
141+
// So if the left shift operation causes an overflow, we just assume the max value
142+
// If necessary, we may be able to improve this bound in the future
143+
if canLShiftOverflow(lUpper, rUpper, exprMaxVal(this).(int))
144+
then result = exprMaxVal(this)
145+
else result = lUpper.bitShiftLeft(rUpper)
146+
)
147+
}
148+
149+
override predicate dependsOnChild(Expr child) {
150+
child = getLeftOperand() or child = getLeftOperand()
151+
}
152+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
private import cpp
2+
private import experimental.semmle.code.cpp.models.interfaces.SimpleRangeAnalysisExpr
3+
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
4+
5+
float evaluateConstantExpr1(Expr e) {
6+
result = e.getValue().toFloat()
7+
or
8+
// This handles when a constant value is put into a variable
9+
// and the variable is used later
10+
exists(SsaDefinition defn, StackVariable sv |
11+
defn.getAUse(sv) = e and
12+
result = defn.getDefiningValue(sv).getValue().toFloat()
13+
)
14+
}
15+
16+
// If the constant right operand is negative or is greater than or equal to the number of
17+
// bits in the left operands type, then the result is undefined (except on the IA-32
18+
// architecture where the shift value is masked with 0b00011111, but we can't
19+
// assume the architecture).
20+
bindingset[val]
21+
pragma[inline]
22+
private predicate isValidShiftExprShift(float val, Expr l) {
23+
val >= 0 and
24+
// We use getFullyConverted because the spec says to use the *promoted* left operand
25+
val < (l.getFullyConverted().getUnderlyingType().getSize() * 8)
26+
}
27+
28+
/**
29+
* This handles the `>>` and `>>=` operators when at least one operand is a constant (and if the
30+
* right operand is a constant, it must be "valid" (see `isValidShiftExprShift`)). When handling any
31+
* undefined behavior, it leaves the values unconstrained. From the C++ standard: "The behavior is
32+
* undefined if the right operand is negative, or greater than or equal to the length in bits of the
33+
* promoted left operand. The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an
34+
* unsigned type or if E1 has a signed type and a non-negative value, the value of the result is the
35+
* integral part of the quotient of E1/2^E2. If E1 has a signed type and a negative value, the
36+
* resulting value is implementation-defined."
37+
*/
38+
class ConstantRShiftExprRange extends SimpleRangeAnalysisExpr {
39+
/**
40+
* Holds for `a >> b` or `a >>= b` in one of the following two cases:
41+
* 1. `a` is a constant and `b` is not
42+
* 2. `b` is constant
43+
*
44+
* We don't handle the case where `a` and `b` are both non-constant values.
45+
*/
46+
ConstantRShiftExprRange() {
47+
getUnspecifiedType() instanceof IntegralType and
48+
exists(Expr l, Expr r |
49+
l = this.(RShiftExpr).getLeftOperand() and
50+
r = this.(RShiftExpr).getRightOperand()
51+
or
52+
l = this.(AssignRShiftExpr).getLValue() and
53+
r = this.(AssignRShiftExpr).getRValue()
54+
|
55+
l.getUnspecifiedType() instanceof IntegralType and
56+
r.getUnspecifiedType() instanceof IntegralType and
57+
(
58+
// If the left operand is a constant, verify that the right operand is not a constant
59+
exists(evaluateConstantExpr1(l)) and not exists(evaluateConstantExpr1(r))
60+
or
61+
// If the right operand is a constant, check if it is a valid shift expression
62+
exists(float constROp |
63+
constROp = evaluateConstantExpr1(r) and isValidShiftExprShift(constROp, l)
64+
)
65+
)
66+
)
67+
}
68+
69+
Expr getLeftOperand() {
70+
result = this.(RShiftExpr).getLeftOperand() or
71+
result = this.(AssignRShiftExpr).getLValue()
72+
}
73+
74+
Expr getRightOperand() {
75+
result = this.(RShiftExpr).getRightOperand() or
76+
result = this.(AssignRShiftExpr).getRValue()
77+
}
78+
79+
override float getLowerBounds() {
80+
exists(int lLower, int lUpper, int rLower, int rUpper |
81+
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
82+
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
83+
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
84+
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
85+
lLower <= lUpper and
86+
rLower <= rUpper
87+
|
88+
if
89+
lLower < 0
90+
or
91+
not (
92+
isValidShiftExprShift(rLower, getLeftOperand()) and
93+
isValidShiftExprShift(rUpper, getLeftOperand())
94+
)
95+
then
96+
// We don't want to deal with shifting negative numbers at the moment,
97+
// and a negative shift is implementation defined, so we set the result
98+
// to the minimum value
99+
result = exprMinVal(this)
100+
else
101+
// We can get the smallest value by shifting the smallest bound by the largest bound
102+
result = lLower.bitShiftRight(rUpper)
103+
)
104+
}
105+
106+
override float getUpperBounds() {
107+
exists(int lLower, int lUpper, int rLower, int rUpper |
108+
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
109+
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
110+
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
111+
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
112+
lLower <= lUpper and
113+
rLower <= rUpper
114+
|
115+
if
116+
lLower < 0
117+
or
118+
not (
119+
isValidShiftExprShift(rLower, getLeftOperand()) and
120+
isValidShiftExprShift(rUpper, getLeftOperand())
121+
)
122+
then
123+
// We don't want to deal with shifting negative numbers at the moment,
124+
// and a negative shift is implementation defined, so we set the result
125+
// to the maximum value
126+
result = exprMaxVal(this)
127+
else
128+
// We can get the largest value by shifting the largest bound by the smallest bound
129+
result = lUpper.bitShiftRight(rLower)
130+
)
131+
}
132+
133+
override predicate dependsOnChild(Expr child) {
134+
child = getLeftOperand() or child = getLeftOperand()
135+
}
136+
}

0 commit comments

Comments
 (0)