Skip to content

Commit 1467d6b

Browse files
committed
Python: Test all expressions that incur dataflow
1 parent 4345b16 commit 1467d6b

File tree

3 files changed

+195
-44
lines changed

3 files changed

+195
-44
lines changed
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
| test.py:20:9:20:14 | ControlFlowNode for SOURCE | test.py:21:10:21:10 | ControlFlowNode for x |
2-
| test.py:25:9:25:16 | ControlFlowNode for Str | test.py:26:10:26:10 | ControlFlowNode for x |
3-
| test.py:29:9:29:17 | ControlFlowNode for Str | test.py:30:10:30:10 | ControlFlowNode for x |
4-
| test.py:33:9:33:10 | ControlFlowNode for IntegerLiteral | test.py:34:10:34:10 | ControlFlowNode for x |
5-
| test.py:37:9:37:12 | ControlFlowNode for FloatLiteral | test.py:38:10:38:10 | ControlFlowNode for x |
6-
| test.py:46:10:46:15 | ControlFlowNode for SOURCE | test.py:47:10:47:10 | ControlFlowNode for x |
1+
| test.py:23:9:23:14 | ControlFlowNode for SOURCE | test.py:24:10:24:10 | ControlFlowNode for x |
2+
| test.py:28:9:28:16 | ControlFlowNode for Str | test.py:29:10:29:10 | ControlFlowNode for x |
3+
| test.py:32:9:32:17 | ControlFlowNode for Str | test.py:33:10:33:10 | ControlFlowNode for x |
4+
| test.py:36:9:36:10 | ControlFlowNode for IntegerLiteral | test.py:37:10:37:10 | ControlFlowNode for x |
5+
| test.py:40:9:40:12 | ControlFlowNode for FloatLiteral | test.py:41:10:41:10 | ControlFlowNode for x |
6+
| test.py:49:10:49:15 | ControlFlowNode for SOURCE | test.py:50:10:50:10 | ControlFlowNode for x |
7+
| test.py:226:15:226:20 | ControlFlowNode for SOURCE | test.py:226:10:226:21 | ControlFlowNode for f() |
8+
| test.py:280:12:280:17 | ControlFlowNode for SOURCE | test.py:280:10:280:18 | ControlFlowNode for f() |
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
| test.py:13:5:13:5 | SSA variable x | test.py:12:1:12:33 | Exit node for Function test_tuple_with_local_flow |
2-
| test.py:13:5:13:5 | SSA variable x | test.py:14:9:14:9 | ControlFlowNode for x |
3-
| test.py:13:10:13:18 | ControlFlowNode for Tuple | test.py:13:5:13:5 | SSA variable x |
4-
| test.py:14:5:14:5 | SSA variable y | test.py:15:5:15:11 | SSA variable y |
5-
| test.py:14:5:14:5 | SSA variable y | test.py:15:10:15:10 | ControlFlowNode for y |
6-
| test.py:14:9:14:12 | ControlFlowNode for Subscript | test.py:14:5:14:5 | SSA variable y |
7-
| test.py:15:5:15:11 | SSA variable y | test.py:12:1:12:33 | Exit node for Function test_tuple_with_local_flow |
1+
| test.py:17:5:17:5 | SSA variable x | test.py:16:1:16:33 | Exit node for Function test_tuple_with_local_flow |
2+
| test.py:17:5:17:5 | SSA variable x | test.py:18:9:18:9 | ControlFlowNode for x |
3+
| test.py:17:10:17:18 | ControlFlowNode for Tuple | test.py:17:5:17:5 | SSA variable x |
4+
| test.py:18:5:18:5 | SSA variable y | test.py:19:5:19:11 | SSA variable y |
5+
| test.py:18:5:18:5 | SSA variable y | test.py:19:10:19:10 | ControlFlowNode for y |
6+
| test.py:18:9:18:12 | ControlFlowNode for Subscript | test.py:18:5:18:5 | SSA variable y |
7+
| test.py:19:5:19:11 | SSA variable y | test.py:16:1:16:33 | Exit node for Function test_tuple_with_local_flow |

python/ql/test/experimental/dataflow/coverage/test.py

Lines changed: 180 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
# This should cover all the syntactical constructs that we hope to support
1+
# This should cover all the syntactical constructs that we hope to support.
2+
# Headings refer to https://docs.python.org/3/reference/expressions.html,
3+
# and are selected whenever they incur dataflow.
24
# Intended sources should be the variable `SOURCE` and intended sinks should be
35
# arguments to the function `SINK` (see python/ql/test/experimental/dataflow/testConfig.qll).
46
#
57
# Functions whose name ends with "_with_local_flow" will also be tested for local flow.
8+
#
9+
# All functions starting with "test_" should run and print a source (sources are defined in testConfig.qll).
610

7-
# These are included so that we can easily evaluate the test code
11+
# These are defined so that we can evaluate the test code.
812
SOURCE = "source"
913
def SINK(x):
1014
print(x)
@@ -14,7 +18,6 @@ def test_tuple_with_local_flow():
1418
y = x[1]
1519
SINK(y)
1620

17-
# List taken from https://docs.python.org/3/reference/expressions.html
1821
# 6.2.1. Identifiers (Names)
1922
def test_names():
2023
x = SOURCE
@@ -90,42 +93,188 @@ def test_generator():
9093
x = (SOURCE for y in [3])
9194
SINK([*x][0])
9295

93-
# List taken from https://docs.python.org/3/reference/expressions.html
94-
# 6. Expressions
95-
# 6.1. Arithmetic conversions
96-
# 6.2. Atoms
97-
# 6.2.1. Identifiers (Names)
98-
# 6.2.2. Literals
99-
# 6.2.3. Parenthesized forms
100-
# 6.2.4. Displays for lists, sets and dictionaries
101-
# 6.2.5. List displays
102-
# 6.2.6. Set displays
103-
# 6.2.7. Dictionary displays
104-
# 6.2.8. Generator expressions
10596
# 6.2.9. Yield expressions
97+
def gen(x):
98+
yield x
99+
100+
def test_yield():
101+
g = gen(SOURCE)
102+
SINK(next(g))
103+
104+
def gen_from(x):
105+
yield from gen(x)
106+
107+
def test_yield_from():
108+
g = gen_from(SOURCE)
109+
SINK(next(g))
110+
111+
# a statement rather than an expression, but related to generators
112+
def test_for():
113+
for x in gen(SOURCE):
114+
SINK(x)
115+
106116
# 6.2.9.1. Generator-iterator methods
107-
# 6.2.9.2. Examples
117+
def test___next__():
118+
g = gen(SOURCE)
119+
SINK(g.__next__())
120+
121+
def gen2(x):
122+
m = yield x # argument of `send` has to flow to value of `yield x` (and so to `m`)
123+
yield m
124+
125+
def test_send():
126+
g = gen2(3)
127+
n = next(g)
128+
SINK(g.send(SOURCE))
129+
130+
def gen_ex(x):
131+
try:
132+
yield 3
133+
except:
134+
yield x # `x` has to flow to call to `throw`
135+
136+
def test_throw():
137+
g = gen_ex(SOURCE)
138+
n = next(g)
139+
SINK(g.throw(TypeError))
140+
141+
# no `test_close` as `close` involves no data flow
142+
108143
# 6.2.9.3. Asynchronous generator functions
144+
async def agen(x):
145+
yield x
146+
109147
# 6.2.9.4. Asynchronous generator-iterator methods
110-
# 6.3. Primaries
148+
149+
# helper to run async test functions
150+
def runa(a):
151+
import asyncio
152+
asyncio.run(a)
153+
154+
async def atest___anext__():
155+
g = agen(SOURCE)
156+
SINK(await g.__anext__())
157+
158+
def test___anext__():
159+
runa(atest___anext__())
160+
161+
async def agen2(x):
162+
m = yield x # argument of `send` has to flow to value of `yield x` (and so to `m`)
163+
yield m
164+
165+
async def atest_asend():
166+
g = agen2(3)
167+
n = await g.__anext__()
168+
SINK(await g.asend(SOURCE))
169+
170+
def test_asend():
171+
runa(atest_asend())
172+
173+
async def agen_ex(x):
174+
try:
175+
yield 3
176+
except:
177+
yield x # `x` has to flow to call to `athrow`
178+
179+
async def atest_athrow():
180+
g = agen_ex(SOURCE)
181+
n = await g.__anext__()
182+
SINK(await g.athrow(TypeError))
183+
184+
def test_athrow():
185+
runa(atest_athrow())
186+
111187
# 6.3.1. Attribute references
188+
class C:
189+
a = SOURCE
190+
191+
def test_attribute_reference():
192+
SINK(C.a)
193+
194+
# overriding __getattr__ should be tested by the class coverage tests
195+
112196
# 6.3.2. Subscriptions
197+
# This does not constitute dataflow (but could be taint flow)
198+
def example_subscription_string():
199+
SINK("source"[0])
200+
201+
def test_subscription_tuple():
202+
SINK((SOURCE,)[0])
203+
204+
def test_subscription_list():
205+
SINK([SOURCE][0])
206+
207+
def test_subscription_mapping():
208+
SINK({"s":SOURCE}["s"])
209+
210+
# overriding __getitem__ should be tested by the class coverage tests
211+
113212
# 6.3.3. Slicings
213+
l = [SOURCE]
214+
215+
def test_slicing():
216+
s = l[0:1:1]
217+
SINK(s[0])
218+
219+
# The grammar seems to allow `l[0:1:1, 0:1]`, but the interpreter does not like it
220+
114221
# 6.3.4. Calls
115-
# 6.4. Await expression
116-
# 6.5. The power operator
117-
# 6.6. Unary arithmetic and bitwise operations
118-
# 6.7. Binary arithmetic operations
119-
# 6.8. Shifting operations
120-
# 6.9. Binary bitwise operations
121-
# 6.10. Comparisons
122-
# 6.10.1. Value comparisons
123-
# 6.10.2. Membership test operations
124-
# 6.10.3. Identity comparisons
125-
# 6.11. Boolean operations
222+
def f(a, b):
223+
return b
224+
225+
def test_call_positional():
226+
SINK(f(3, SOURCE))
227+
228+
def test_call_keyword():
229+
SINK(f(3, b=SOURCE))
230+
231+
def test_call_unpack_iterable():
232+
SINK(f(3, *[SOURCE]))
233+
234+
def test_call_unpack_mapping():
235+
SINK(f(3, **{"b": SOURCE}))
236+
237+
def f_extra_pos(a, *b):
238+
return b[0]
239+
240+
def test_call_extra_pos():
241+
SINK(f_extra_pos(3, SOURCE))
242+
243+
def f_extra_keyword(a, **b):
244+
return b["b"]
245+
246+
def test_call_extra_keyword():
247+
SINK(f_extra_keyword(3, b=SOURCE))
248+
249+
# return the name of the first extra keyword argument
250+
def f_extra_keyword_flow(**a):
251+
return [*a][0]
252+
253+
# call the function with our source as the name of the keyword arguemnt
254+
def test_call_extra_keyword_flow():
255+
SINK(f_extra_keyword_flow(**{SOURCE: None}))
256+
126257
# 6.12. Assignment expressions
258+
def test_assignment_expression():
259+
x = 3
260+
SINK(x := SOURCE)
261+
127262
# 6.13. Conditional expressions
263+
def test_conditional_true():
264+
SINK(SOURCE if True else 3)
265+
266+
def test_conditional_false():
267+
SINK(3 if False else SOURCE)
268+
269+
def test_conditional_evaluation_true():
270+
x = 3
271+
SINK(x if (SOURCE == (x := SOURCE)) else 3) # Condition is evaluated first, so x is SOURCE once chosen
272+
273+
def test_conditional_evaluation_false():
274+
x = 3
275+
SINK(3 if (3 == (x := SOURCE)) else x) # Condition is evaluated first, so x is SOURCE once chosen
276+
128277
# 6.14. Lambdas
129-
# 6.15. Expression lists
130-
# 6.16. Evaluation order
131-
# 6.17. Operator precedence
278+
def test_lambda():
279+
f = lambda x : x
280+
SINK(f(SOURCE))

0 commit comments

Comments
 (0)