Skip to content

Commit 0d38ff8

Browse files
authored
Merge pull request github#11920 from gsingh93/bit-shift-range
C++: Improve left shift and right shift range analysis accuracy
2 parents 8bc9ce7 + 1a109ca commit 0d38ff8

File tree

4 files changed

+478
-0
lines changed

4 files changed

+478
-0
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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 evaluateConstantExpr(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+
private predicate isValidShiftExprShift(float val, Expr l) {
22+
val >= 0 and
23+
// We use getFullyConverted because the spec says to use the *promoted* left operand
24+
val < (l.getFullyConverted().getUnderlyingType().getSize() * 8)
25+
}
26+
27+
bindingset[val, shift, max_val]
28+
private predicate canLShiftOverflow(int val, int shift, int max_val) {
29+
// val << shift = val * 2^shift > max_val => val > max_val/2^shift = max_val >> b
30+
val > max_val.bitShiftRight(shift)
31+
}
32+
33+
/**
34+
* A range analysis expression consisting of the `>>` or `>>=` operator when at least
35+
* one operand is a constant (and if the right operand is a constant, it must be "valid"
36+
* (see `isValidShiftExprShift`)). When handling any undefined behavior, it leaves the
37+
* 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
39+
* left operand. The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an
40+
* unsigned type or if E1 has a signed type and a non-negative value, the value of the
41+
* result is the integral part of the quotient of E1/2^E2. If E1 has a signed type and a
42+
* negative value, the resulting value is implementation-defined."
43+
*/
44+
class ConstantRShiftExprRange extends SimpleRangeAnalysisExpr {
45+
/**
46+
* Holds for `a >> b` or `a >>= b` in one of the following two cases:
47+
* 1. `a` is a constant and `b` is not
48+
* 2. `b` is constant
49+
*
50+
* We don't handle the case where `a` and `b` are both non-constant values.
51+
*/
52+
ConstantRShiftExprRange() {
53+
getUnspecifiedType() instanceof IntegralType and
54+
exists(Expr l, Expr r |
55+
l = this.(RShiftExpr).getLeftOperand() and
56+
r = this.(RShiftExpr).getRightOperand()
57+
or
58+
l = this.(AssignRShiftExpr).getLValue() and
59+
r = this.(AssignRShiftExpr).getRValue()
60+
|
61+
l.getUnspecifiedType() instanceof IntegralType and
62+
r.getUnspecifiedType() instanceof IntegralType and
63+
(
64+
// If the left operand is a constant, verify that the right operand is not a constant
65+
exists(evaluateConstantExpr(l)) and not exists(evaluateConstantExpr(r))
66+
or
67+
// If the right operand is a constant, check if it is a valid shift expression
68+
exists(float constROp |
69+
constROp = evaluateConstantExpr(r) and isValidShiftExprShift(constROp, l)
70+
)
71+
)
72+
)
73+
}
74+
75+
Expr getLeftOperand() {
76+
result = this.(RShiftExpr).getLeftOperand() or
77+
result = this.(AssignRShiftExpr).getLValue()
78+
}
79+
80+
Expr getRightOperand() {
81+
result = this.(RShiftExpr).getRightOperand() or
82+
result = this.(AssignRShiftExpr).getRValue()
83+
}
84+
85+
override float getLowerBounds() {
86+
exists(int lLower, int lUpper, int rLower, int rUpper |
87+
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
88+
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
89+
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
90+
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
91+
lLower <= lUpper and
92+
rLower <= rUpper
93+
|
94+
if
95+
lLower < 0
96+
or
97+
not (
98+
isValidShiftExprShift(rLower, getLeftOperand()) and
99+
isValidShiftExprShift(rUpper, getLeftOperand())
100+
)
101+
then
102+
// We don't want to deal with shifting negative numbers at the moment,
103+
// and a negative shift is implementation defined, so we set the result
104+
// to the minimum value
105+
result = exprMinVal(this)
106+
else
107+
// We can get the smallest value by shifting the smallest bound by the largest bound
108+
result = lLower.bitShiftRight(rUpper)
109+
)
110+
}
111+
112+
override float getUpperBounds() {
113+
exists(int lLower, int lUpper, int rLower, int rUpper |
114+
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
115+
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
116+
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
117+
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
118+
lLower <= lUpper and
119+
rLower <= rUpper
120+
|
121+
if
122+
lLower < 0
123+
or
124+
not (
125+
isValidShiftExprShift(rLower, getLeftOperand()) and
126+
isValidShiftExprShift(rUpper, getLeftOperand())
127+
)
128+
then
129+
// We don't want to deal with shifting negative numbers at the moment,
130+
// and a negative shift is implementation defined, so we set the result
131+
// to the maximum value
132+
result = exprMaxVal(this)
133+
else
134+
// We can get the largest value by shifting the largest bound by the smallest bound
135+
result = lUpper.bitShiftRight(rLower)
136+
)
137+
}
138+
139+
override predicate dependsOnChild(Expr child) {
140+
child = getLeftOperand() or child = getRightOperand()
141+
}
142+
}
143+
144+
/**
145+
* A range analysis expression consisting of the `<<` or `<<=` operator when at least
146+
* one operand is a constant (and if the right operand is a constant, it must be "valid"
147+
* (see `isValidShiftExprShift`)). When handling any undefined behavior, it leaves the
148+
* values unconstrained. From the C++ standard: "The behavior is undefined if the right
149+
* operand is negative, or greater than or equal to the length in bits of the promoted left operand.
150+
* The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1
151+
* has an unsigned type, the value of the result is E1 x 2 E2, reduced modulo one more than the
152+
* maximum value representable in the result type. Otherwise, if E1 has a signed type and
153+
* non-negative value, and E1 x 2 E2 is representable in the corresponding unsigned type of the
154+
* result type, then that value, converted to the result type, is the resulting value; otherwise,
155+
* the behavior is undefined."
156+
*/
157+
class ConstantLShiftExprRange extends SimpleRangeAnalysisExpr {
158+
/**
159+
* Holds for `a << b` or `a <<= b` in one of the following two cases:
160+
* 1. `a` is a constant and `b` is not
161+
* 2. `b` is constant
162+
*
163+
* We don't handle the case where `a` and `b` are both non-constant values.
164+
*/
165+
ConstantLShiftExprRange() {
166+
getUnspecifiedType() instanceof IntegralType and
167+
exists(Expr l, Expr r |
168+
l = this.(LShiftExpr).getLeftOperand() and
169+
r = this.(LShiftExpr).getRightOperand()
170+
or
171+
l = this.(AssignLShiftExpr).getLValue() and
172+
r = this.(AssignLShiftExpr).getRValue()
173+
|
174+
l.getUnspecifiedType() instanceof IntegralType and
175+
r.getUnspecifiedType() instanceof IntegralType and
176+
(
177+
// If the left operand is a constant, verify that the right operand is not a constant
178+
exists(evaluateConstantExpr(l)) and not exists(evaluateConstantExpr(r))
179+
or
180+
// If the right operand is a constant, check if it is a valid shift expression
181+
exists(float constROp |
182+
constROp = evaluateConstantExpr(r) and isValidShiftExprShift(constROp, l)
183+
)
184+
)
185+
)
186+
}
187+
188+
Expr getLeftOperand() {
189+
result = this.(LShiftExpr).getLeftOperand() or
190+
result = this.(AssignLShiftExpr).getLValue()
191+
}
192+
193+
Expr getRightOperand() {
194+
result = this.(LShiftExpr).getRightOperand() or
195+
result = this.(AssignLShiftExpr).getRValue()
196+
}
197+
198+
override float getLowerBounds() {
199+
exists(int lLower, int lUpper, int rLower, int rUpper |
200+
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
201+
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
202+
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
203+
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
204+
lLower <= lUpper and
205+
rLower <= rUpper
206+
|
207+
if
208+
lLower < 0
209+
or
210+
not (
211+
isValidShiftExprShift(rLower, getLeftOperand()) and
212+
isValidShiftExprShift(rUpper, getLeftOperand())
213+
)
214+
then
215+
// We don't want to deal with shifting negative numbers at the moment,
216+
// and a negative shift is undefined, so we set to the minimum value
217+
result = exprMinVal(this)
218+
else
219+
// If we have `0b01010000 << [0, 2]`, the max value for 8 bits is 0b10100000
220+
// (a shift of 1) but doing a shift by the upper bound would give 0b01000000.
221+
// So if the left shift operation causes an overflow, we just assume the max value
222+
// If necessary, we may be able to improve this bound in the future
223+
if canLShiftOverflow(lUpper, rUpper, exprMaxVal(this))
224+
then result = exprMinVal(this)
225+
else result = lLower.bitShiftLeft(rLower)
226+
)
227+
}
228+
229+
override float getUpperBounds() {
230+
exists(int lLower, int lUpper, int rLower, int rUpper |
231+
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
232+
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
233+
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
234+
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
235+
lLower <= lUpper and
236+
rLower <= rUpper
237+
|
238+
if
239+
lLower < 0
240+
or
241+
not (
242+
isValidShiftExprShift(rLower, getLeftOperand()) and
243+
isValidShiftExprShift(rUpper, getLeftOperand())
244+
)
245+
then
246+
// We don't want to deal with shifting negative numbers at the moment,
247+
// and a negative shift is undefined, so we set it to the maximum value
248+
result = exprMaxVal(this)
249+
else
250+
// If we have `0b01010000 << [0, 2]`, the max value for 8 bits is 0b10100000
251+
// (a shift of 1) but doing a shift by the upper bound would give 0b01000000.
252+
// So if the left shift operation causes an overflow, we just assume the max value
253+
// If necessary, we may be able to improve this bound in the future
254+
if canLShiftOverflow(lUpper, rUpper, exprMaxVal(this))
255+
then result = exprMaxVal(this)
256+
else result = lUpper.bitShiftLeft(rUpper)
257+
)
258+
}
259+
260+
override predicate dependsOnChild(Expr child) {
261+
child = getLeftOperand() or child = getRightOperand()
262+
}
263+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
| bitshift.cpp:23:3:23:9 | ... <<= ... | 0.0 | 255.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int |
2+
| bitshift.cpp:25:5:25:11 | ... <<= ... | 0.0 | 240.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int |
3+
| bitshift.cpp:29:3:29:8 | ... << ... | 0.0 | 1020.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
4+
| bitshift.cpp:32:3:32:9 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
5+
| bitshift.cpp:35:3:35:9 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
6+
| bitshift.cpp:38:3:38:22 | ... << ... | 0.0 | 32640.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
7+
| bitshift.cpp:39:3:39:22 | ... << ... | 0.0 | 32640.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
8+
| bitshift.cpp:40:3:40:22 | ... << ... | 0.0 | 32640.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
9+
| bitshift.cpp:43:3:43:19 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
10+
| bitshift.cpp:46:3:46:22 | ... << ... | 128.0 | 128.0 | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
11+
| bitshift.cpp:49:3:49:8 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
12+
| bitshift.cpp:52:5:52:10 | ... << ... | 1.0 | 128.0 | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
13+
| bitshift.cpp:57:3:57:8 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
14+
| bitshift.cpp:58:3:58:9 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
15+
| bitshift.cpp:59:3:59:9 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
16+
| bitshift.cpp:60:3:60:22 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
17+
| bitshift.cpp:61:3:61:19 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
18+
| bitshift.cpp:64:3:64:19 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | int | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
19+
| bitshift.cpp:67:3:67:8 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | int | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
20+
| bitshift.cpp:70:5:70:10 | ... << ... | 1.0 | 128.0 | file://:0:0:0:0 | int | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
21+
| bitshift.cpp:75:5:75:10 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
22+
| bitshift.cpp:76:5:76:10 | ... << ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
23+
| bitshift.cpp:90:3:90:9 | ... >>= ... | 0.0 | 63.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int |
24+
| bitshift.cpp:92:5:92:11 | ... >>= ... | 0.0 | 15.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int |
25+
| bitshift.cpp:96:3:96:8 | ... >> ... | 0.0 | 63.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
26+
| bitshift.cpp:99:3:99:9 | ... >> ... | 0.0 | 0.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
27+
| bitshift.cpp:103:3:103:9 | ... >> ... | 0.0 | 0.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
28+
| bitshift.cpp:106:3:106:22 | ... >> ... | 0.0 | 63.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
29+
| bitshift.cpp:107:3:107:22 | ... >> ... | 0.0 | 63.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
30+
| bitshift.cpp:108:3:108:22 | ... >> ... | 0.0 | 63.0 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
31+
| bitshift.cpp:111:3:111:19 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
32+
| bitshift.cpp:114:3:114:24 | ... >> ... | 32.0 | 32.0 | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
33+
| bitshift.cpp:117:3:117:10 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
34+
| bitshift.cpp:120:5:120:12 | ... >> ... | 32.0 | 128.0 | file://:0:0:0:0 | int | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
35+
| bitshift.cpp:126:3:126:8 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
36+
| bitshift.cpp:127:3:127:9 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
37+
| bitshift.cpp:128:3:128:9 | ... >> ... | -1.0 | 0.0 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
38+
| bitshift.cpp:129:3:129:22 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
39+
| bitshift.cpp:130:3:130:19 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
40+
| bitshift.cpp:133:3:133:21 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | int | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
41+
| bitshift.cpp:136:3:136:10 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | int | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
42+
| bitshift.cpp:139:5:139:12 | ... >> ... | 32.0 | 128.0 | file://:0:0:0:0 | int | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
43+
| bitshift.cpp:144:5:144:10 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | signed char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
44+
| bitshift.cpp:145:5:145:10 | ... >> ... | -2.147483648E9 | 2.147483647E9 | file://:0:0:0:0 | signed char | file://:0:0:0:0 | unsigned char | file://:0:0:0:0 | int | file://:0:0:0:0 | int |
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import cpp
2+
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
3+
import experimental.semmle.code.cpp.rangeanalysis.extensions.ConstantShiftExprRange
4+
5+
Expr getLOp(Operation o) {
6+
result = o.(BinaryOperation).getLeftOperand() or
7+
result = o.(Assignment).getLValue()
8+
}
9+
10+
Expr getROp(Operation o) {
11+
result = o.(BinaryOperation).getRightOperand() or
12+
result = o.(Assignment).getRValue()
13+
}
14+
15+
from Operation o
16+
where
17+
(
18+
o instanceof BinaryBitwiseOperation
19+
or
20+
o instanceof AssignBitwiseOperation
21+
)
22+
select o, lowerBound(o), upperBound(o), getLOp(o).getUnderlyingType(),
23+
getROp(o).getUnderlyingType(), getLOp(o).getFullyConverted().getUnderlyingType(),
24+
getROp(o).getFullyConverted().getUnderlyingType()

0 commit comments

Comments
 (0)