Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e099078

Browse files
authored
Merge pull request #2817 from BekaValentine/objectapi-to-valueapi-truncateddivision
Python: ObjectAPI to ValueAPI: TruncatedDivision
2 parents 9c06c48 + b049345 commit e099078

4 files changed

Lines changed: 86 additions & 21 deletions

File tree

python/ql/src/Expressions/TruncatedDivision.ql

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,20 @@ where
1818
// Only relevant for Python 2, as all later versions implement true division
1919
major_version() = 2
2020
and
21-
exists(BinaryExprNode bin, Object lobj, Object robj |
21+
exists(BinaryExprNode bin, Value lval, Value rval |
2222
bin = div.getAFlowNode()
2323
and bin.getNode().getOp() instanceof Div
24-
and bin.getLeft().refersTo(lobj, theIntType(), left)
25-
and bin.getRight().refersTo(robj, theIntType(), right)
24+
and bin.getLeft().pointsTo(lval, left)
25+
and lval.getClass() = ClassValue::int_()
26+
and bin.getRight().pointsTo(rval, right)
27+
and rval.getClass() = ClassValue::int_()
2628
// Ignore instances where integer division leaves no remainder
27-
and not lobj.(NumericObject).intValue() % robj.(NumericObject).intValue() = 0
29+
and not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0
2830
and not bin.getNode().getEnclosingModule().hasFromFuture("division")
2931
// Filter out results wrapped in `int(...)`
30-
and not exists(CallNode c, ClassObject cls |
31-
c.getAnArg() = bin
32-
and c.getFunction().refersTo(cls)
33-
and cls.getName() = "int"
32+
and not exists(CallNode c |
33+
c = ClassValue::int_().getACall()
34+
and c.getAnArg() = bin
3435
)
3536
)
3637
select div, "Result of division may be truncated as its $@ and $@ arguments may both be integers.",

python/ql/src/semmle/python/objects/ObjectAPI.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class Value extends TObject {
7878
predicate isBuiltin() {
7979
this.(ObjectInternal).isBuiltin()
8080
}
81-
81+
8282
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
8383
this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec)
8484
or
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
| TruncatedDivision_test.py:8:12:8:16 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:8:12:8:12 | TruncatedDivision_test.py:8 | left | TruncatedDivision_test.py:8:16:8:16 | TruncatedDivision_test.py:8 | right |
2-
| TruncatedDivision_test.py:11:12:11:40 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:2:12:2:12 | TruncatedDivision_test.py:2 | left | TruncatedDivision_test.py:5:12:5:12 | TruncatedDivision_test.py:5 | right |
1+
| TruncatedDivision_test.py:65:7:65:11 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:65:7:65:7 | TruncatedDivision_test.py:65 | left | TruncatedDivision_test.py:65:11:65:11 | TruncatedDivision_test.py:65 | right |
2+
| TruncatedDivision_test.py:72:7:72:35 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:25:12:25:12 | TruncatedDivision_test.py:25 | left | TruncatedDivision_test.py:28:12:28:12 | TruncatedDivision_test.py:28 | right |
Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,88 @@
1+
#### TruncatedDivision.ql
2+
3+
# NOTE: The following test case will only work under Python 2.
4+
5+
# Truncated division occurs when two integers are divided. This causes the
6+
# fractional part, if there is one, to be discared. So for example, `2 / 3` will
7+
# evaluate to `0` instead of `0.666...`.
8+
9+
10+
11+
12+
13+
## Negative Cases
14+
15+
16+
17+
# This case is good, and is a minimal obvious case that should be good. It
18+
# SHOULD NOT be found by the query.
19+
print(3.0 / 2.0)
20+
21+
# This case is good, because it explicitly converts the possibly-truncated
22+
# value to an integer. It SHOULD NOT be found by the query.
23+
124
def return_three():
225
return 3
326

427
def return_two():
528
return 2
629

7-
def f1():
8-
return 3 / 2
30+
print(int(return_three() / return_two()))
31+
32+
933

10-
def f2():
11-
return return_three() / return_two()
34+
# These cases are good, because `halve` checks the type, and if the type would
35+
# truncate, it explicitly converts to a float first before doing the division.
36+
# These SHOULD NOT be found by the query.
1237

13-
def f3(x):
38+
def halve(x):
1439
if isinstance(x, float):
1540
return x / 2
1641
else:
1742
return (1.0 * x) / 2
1843

19-
def f4():
20-
do_stuff(f3(1))
21-
do_stuff(f3(1.0))
44+
print(halve(1))
45+
print(halve(1.0))
46+
47+
48+
49+
# This case is good, because the sum is `3.0`, which is a float, and will not
50+
# truncate. This case SHOULD NOT be found by the query.
51+
52+
print(average([1.0, 2.0]))
53+
54+
55+
56+
57+
58+
## Positive Cases
59+
60+
61+
62+
# This case is bad, and is a minimal obvious case that should be bad. It
63+
# SHOULD be found by the query.
64+
65+
print(3 / 2)
66+
67+
68+
69+
# This case is bad. It uses indirect returns of integers through function calls
70+
# to produce the problem. I
71+
72+
print(return_three() / return_two())
73+
74+
75+
76+
# This case is bad, because the sum is `3`, which is an integer, and will
77+
# truncate when divided by the length `2`. This case SHOULD be found by the
78+
# query.
79+
80+
# NOTE (2020-02-20):
81+
# The current version of the Value/pointsTo API doesn't permit this detection,
82+
# unfortunately, but we preserve this example in the hopes that future
83+
# versions will catch it. That will necessitate changing the expected results.
84+
85+
def average(l):
86+
return sum(l) / len(l)
2287

23-
def f5():
24-
return int(return_three() / return_two())
88+
print(average([1,2]))

0 commit comments

Comments
 (0)