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

Skip to content

Commit e02cfbf

Browse files
committed
Python: Support keyword overflow arguments
1 parent 27af9bb commit e02cfbf

5 files changed

Lines changed: 80 additions & 7 deletions

File tree

python/ql/src/experimental/dataflow/internal/DataFlowPrivate.qll

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,20 +237,34 @@ private Node update(Node node) {
237237
//
238238
/** Computes routing of arguments to parameters */
239239
module ArgumentPassing {
240+
/**
241+
* Gets the `n`th parameter of `callable`.
242+
* If the callable has a starred parameter, say `*tuple`, that is matched with `n=-1`.
243+
* If the callable has a doubly starred parameter, say `**dict`, that is matched with `n=-2`.
244+
* Note that, unlike other languages, we do _not_ use -1 for the position of `self` in Python,
245+
* as it is an explicit parameter at position 0.
246+
*/
240247
NameNode getParameter(CallableValue callable, int n) {
241248
// positional parameter
242249
result = callable.getParameter(n)
243250
or
244-
// vararg
251+
// starred parameter, `*tuple`
245252
exists(Function f |
246253
f = callable.getScope() and
247-
n = f.getPositionalParameterCount() and
254+
n = -1 and
248255
result = f.getVararg().getAFlowNode()
249256
)
257+
or
258+
// doubly starred parameter, `**dict`
259+
exists(Function f |
260+
f = callable.getScope() and
261+
n = -2 and
262+
result = f.getKwarg().getAFlowNode()
263+
)
250264
}
251265

252266
/**
253-
* Gets the argument to `call` then is passed to the `n`th parameter of `callable`.
267+
* Gets the argument to `call` that is passed to the `n`th parameter of `callable`.
254268
*/
255269
Node getArg(CallNode call, CallableValue callable, int n) {
256270
call = callable.getACall() and
@@ -265,16 +279,25 @@ module ArgumentPassing {
265279
result = TCfgNode(call.getArgByName(argName))
266280
)
267281
or
268-
// vararg
282+
// argument -1 is a synthezised argument passed to the starred parameter
269283
exists(Function f |
270284
f = callable.getScope() and
271285
f.hasVarArg() and
272-
n = f.getPositionalParameterCount() and
286+
n = -1 and
273287
result = TPosOverflowNode(call, callable)
274288
)
289+
or
290+
// argument -2 is a synthezised argument passed to the doubly starred parameter
291+
exists(Function f |
292+
f = callable.getScope() and
293+
f.hasKwArg() and
294+
n = -2 and
295+
result = TKwOverflowNode(call, callable)
296+
)
275297
)
276298
}
277299

300+
/** Gets the control flow node that is passed as the `n`th overflow positional argument. */
278301
ControlFlowNode getPositionalOverflowArg(CallNode call, CallableValue callable, int n) {
279302
call = callable.getACall() and
280303
exists(Function f, int posCount, int argNr |
@@ -286,6 +309,17 @@ module ArgumentPassing {
286309
argNr = posCount + n
287310
)
288311
}
312+
313+
/** Gets the control flow node that is passed as the overflow keyword argument with key `key`. */
314+
ControlFlowNode getKeywordOverflowArg(CallNode call, CallableValue callable, string key) {
315+
call = callable.getACall() and
316+
exists(Function f |
317+
f = callable.getScope() and
318+
f.hasKwArg() and
319+
not exists(f.getArgByName(key)) and
320+
result = call.getArgByName(key)
321+
)
322+
}
289323
}
290324

291325
import ArgumentPassing
@@ -594,6 +628,8 @@ predicate storeStep(Node nodeFrom, Content c, Node nodeTo) {
594628
attributeStoreStep(nodeFrom, c, nodeTo)
595629
or
596630
posOverflowStoreStep(nodeFrom, c, nodeTo)
631+
or
632+
kwOverflowStoreStep(nodeFrom, c, nodeTo)
597633
}
598634

599635
/** Data flows from an element of a list to the list. */
@@ -689,6 +725,10 @@ predicate attributeStoreStep(CfgNode nodeFrom, AttributeContent c, PostUpdateNod
689725
)
690726
}
691727

728+
/**
729+
* Holds if `nodeFrom` flows into the synthezised positional overflow argument (`nodeTo`)
730+
* at the position indicated by `c`.
731+
*/
692732
predicate posOverflowStoreStep(CfgNode nodeFrom, TupleElementContent c, Node nodeTo) {
693733
exists(CallNode call, CallableValue callable, int n |
694734
nodeFrom.asCfgNode() = getPositionalOverflowArg(call, callable, n) and
@@ -697,6 +737,18 @@ predicate posOverflowStoreStep(CfgNode nodeFrom, TupleElementContent c, Node nod
697737
)
698738
}
699739

740+
/**
741+
* Holds if `nodeFrom` flows into the synthezised keyword overflow argument (`nodeTo`)
742+
* at the key indicated by `c`.
743+
*/
744+
predicate kwOverflowStoreStep(CfgNode nodeFrom, DictionaryElementContent c, Node nodeTo) {
745+
exists(CallNode call, CallableValue callable, string key |
746+
nodeFrom.asCfgNode() = getKeywordOverflowArg(call, callable, key) and
747+
nodeTo = TKwOverflowNode(call, callable) and
748+
c.getKey() = key
749+
)
750+
}
751+
700752
/**
701753
* Holds if data can flow from `nodeFrom` to `nodeTo` via a read of content `c`.
702754
*/

python/ql/src/experimental/dataflow/internal/DataFlowPublic.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ newtype TNode =
3232
/** A node representing the overflow positional arguments to a call. */
3333
TPosOverflowNode(CallNode call, CallableValue callable) {
3434
exists(getPositionalOverflowArg(call, callable, _))
35+
} or
36+
/** A node representing the overflow keyword arguments to a call. */
37+
TKwOverflowNode(CallNode call, CallableValue callable) {
38+
exists(getKeywordOverflowArg(call, callable, _))
3539
}
3640

3741
/**

python/ql/test/experimental/dataflow/coverage/argumentRouting1.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
| argumentPassing.py:163:13:163:16 | ControlFlowNode for arg1 | argumentPassing.py:161:15:161:15 | ControlFlowNode for a |
1414
| argumentPassing.py:170:16:170:19 | ControlFlowNode for arg1 | argumentPassing.py:168:15:168:15 | ControlFlowNode for a |
1515
| argumentPassing.py:177:15:177:18 | ControlFlowNode for arg1 | argumentPassing.py:175:15:175:15 | ControlFlowNode for a |
16+
| argumentPassing.py:184:23:184:26 | ControlFlowNode for arg1 | argumentPassing.py:182:15:182:20 | ControlFlowNode for Subscript |
1617
| classes.py:563:5:563:16 | SSA variable with_getitem | classes.py:557:15:557:18 | ControlFlowNode for self |
1718
| classes.py:578:5:578:16 | SSA variable with_setitem | classes.py:573:15:573:18 | ControlFlowNode for self |
1819
| classes.py:593:5:593:16 | SSA variable with_delitem | classes.py:588:15:588:18 | ControlFlowNode for self |

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ edges
2828
| datamodel.py:152:14:152:19 | ControlFlowNode for SOURCE | datamodel.py:152:5:152:8 | [post store] ControlFlowNode for self [Attribute b] |
2929
| datamodel.py:155:14:155:25 | ControlFlowNode for Customized() [Attribute b] | datamodel.py:159:6:159:15 | ControlFlowNode for customized [Attribute b] |
3030
| datamodel.py:159:6:159:15 | ControlFlowNode for customized [Attribute b] | datamodel.py:159:6:159:17 | ControlFlowNode for Attribute |
31+
| file://:0:0:0:0 | Data flow node [Dictionary element at key b] | test.py:397:10:397:45 | ControlFlowNode for f_extra_keyword() |
32+
| file://:0:0:0:0 | Data flow node [Dictionary element at key b] | test.py:487:10:487:45 | ControlFlowNode for f_extra_keyword() |
3133
| file://:0:0:0:0 | Data flow node [Tuple element at index 0] | test.py:389:10:389:39 | ControlFlowNode for f_extra_pos() |
3234
| file://:0:0:0:0 | Data flow node [Tuple element at index 0] | test.py:482:10:482:39 | ControlFlowNode for f_extra_pos() |
3335
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:42:21:42:26 | ControlFlowNode for SOURCE |
@@ -52,10 +54,12 @@ edges
5254
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:365:28:365:33 | ControlFlowNode for SOURCE |
5355
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:373:30:373:35 | ControlFlowNode for SOURCE |
5456
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:389:33:389:38 | ControlFlowNode for SOURCE |
57+
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:397:39:397:44 | ControlFlowNode for SOURCE |
5558
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:442:12:442:17 | ControlFlowNode for SOURCE |
5659
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:449:28:449:33 | ControlFlowNode for SOURCE |
5760
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:463:30:463:35 | ControlFlowNode for SOURCE |
5861
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:482:33:482:38 | ControlFlowNode for SOURCE |
62+
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:487:39:487:44 | ControlFlowNode for SOURCE |
5963
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | test.py:499:9:499:14 | ControlFlowNode for SOURCE |
6064
| test.py:20:1:20:6 | GSSA Variable SOURCE | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test |
6165
| test.py:20:10:20:17 | ControlFlowNode for Str | test.py:20:1:20:6 | GSSA Variable SOURCE |
@@ -155,10 +159,12 @@ edges
155159
| test.py:365:28:365:33 | ControlFlowNode for SOURCE | test.py:365:10:365:34 | ControlFlowNode for second() |
156160
| test.py:373:30:373:35 | ControlFlowNode for SOURCE | test.py:373:10:373:36 | ControlFlowNode for second() |
157161
| test.py:389:33:389:38 | ControlFlowNode for SOURCE | file://:0:0:0:0 | Data flow node [Tuple element at index 0] |
162+
| test.py:397:39:397:44 | ControlFlowNode for SOURCE | file://:0:0:0:0 | Data flow node [Dictionary element at key b] |
158163
| test.py:442:12:442:17 | ControlFlowNode for SOURCE | test.py:442:10:442:18 | ControlFlowNode for f() |
159164
| test.py:449:28:449:33 | ControlFlowNode for SOURCE | test.py:449:10:449:34 | ControlFlowNode for second() |
160165
| test.py:463:30:463:35 | ControlFlowNode for SOURCE | test.py:463:10:463:36 | ControlFlowNode for second() |
161166
| test.py:482:33:482:38 | ControlFlowNode for SOURCE | file://:0:0:0:0 | Data flow node [Tuple element at index 0] |
167+
| test.py:487:39:487:44 | ControlFlowNode for SOURCE | file://:0:0:0:0 | Data flow node [Dictionary element at key b] |
162168
| test.py:499:9:499:14 | ControlFlowNode for SOURCE | test.py:501:10:501:10 | ControlFlowNode for a |
163169
| test.py:499:9:499:14 | ControlFlowNode for SOURCE | test.py:506:10:506:10 | ControlFlowNode for b |
164170
nodes
@@ -181,6 +187,8 @@ nodes
181187
| datamodel.py:155:14:155:25 | ControlFlowNode for Customized() [Attribute b] | semmle.label | ControlFlowNode for Customized() [Attribute b] |
182188
| datamodel.py:159:6:159:15 | ControlFlowNode for customized [Attribute b] | semmle.label | ControlFlowNode for customized [Attribute b] |
183189
| datamodel.py:159:6:159:17 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
190+
| file://:0:0:0:0 | Data flow node [Dictionary element at key b] | semmle.label | Data flow node [Dictionary element at key b] |
191+
| file://:0:0:0:0 | Data flow node [Dictionary element at key b] | semmle.label | Data flow node [Dictionary element at key b] |
184192
| file://:0:0:0:0 | Data flow node [Tuple element at index 0] | semmle.label | Data flow node [Tuple element at index 0] |
185193
| file://:0:0:0:0 | Data flow node [Tuple element at index 0] | semmle.label | Data flow node [Tuple element at index 0] |
186194
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test | semmle.label | ModuleVariableNode for Global Variable SOURCE in Module test |
@@ -308,6 +316,8 @@ nodes
308316
| test.py:373:30:373:35 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
309317
| test.py:389:10:389:39 | ControlFlowNode for f_extra_pos() | semmle.label | ControlFlowNode for f_extra_pos() |
310318
| test.py:389:33:389:38 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
319+
| test.py:397:10:397:45 | ControlFlowNode for f_extra_keyword() | semmle.label | ControlFlowNode for f_extra_keyword() |
320+
| test.py:397:39:397:44 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
311321
| test.py:442:10:442:18 | ControlFlowNode for f() | semmle.label | ControlFlowNode for f() |
312322
| test.py:442:12:442:17 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
313323
| test.py:449:10:449:34 | ControlFlowNode for second() | semmle.label | ControlFlowNode for second() |
@@ -316,6 +326,8 @@ nodes
316326
| test.py:463:30:463:35 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
317327
| test.py:482:10:482:39 | ControlFlowNode for f_extra_pos() | semmle.label | ControlFlowNode for f_extra_pos() |
318328
| test.py:482:33:482:38 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
329+
| test.py:487:10:487:45 | ControlFlowNode for f_extra_keyword() | semmle.label | ControlFlowNode for f_extra_keyword() |
330+
| test.py:487:39:487:44 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
319331
| test.py:499:9:499:14 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
320332
| test.py:501:10:501:10 | ControlFlowNode for a | semmle.label | ControlFlowNode for a |
321333
| test.py:506:10:506:10 | ControlFlowNode for b | semmle.label | ControlFlowNode for b |
@@ -392,6 +404,8 @@ nodes
392404
| test.py:373:10:373:36 | ControlFlowNode for second() | test.py:373:30:373:35 | ControlFlowNode for SOURCE | test.py:373:10:373:36 | ControlFlowNode for second() | Flow found |
393405
| test.py:389:10:389:39 | ControlFlowNode for f_extra_pos() | test.py:20:10:20:17 | ControlFlowNode for Str | test.py:389:10:389:39 | ControlFlowNode for f_extra_pos() | Flow found |
394406
| test.py:389:10:389:39 | ControlFlowNode for f_extra_pos() | test.py:389:33:389:38 | ControlFlowNode for SOURCE | test.py:389:10:389:39 | ControlFlowNode for f_extra_pos() | Flow found |
407+
| test.py:397:10:397:45 | ControlFlowNode for f_extra_keyword() | test.py:20:10:20:17 | ControlFlowNode for Str | test.py:397:10:397:45 | ControlFlowNode for f_extra_keyword() | Flow found |
408+
| test.py:397:10:397:45 | ControlFlowNode for f_extra_keyword() | test.py:397:39:397:44 | ControlFlowNode for SOURCE | test.py:397:10:397:45 | ControlFlowNode for f_extra_keyword() | Flow found |
395409
| test.py:442:10:442:18 | ControlFlowNode for f() | test.py:20:10:20:17 | ControlFlowNode for Str | test.py:442:10:442:18 | ControlFlowNode for f() | Flow found |
396410
| test.py:442:10:442:18 | ControlFlowNode for f() | test.py:442:12:442:17 | ControlFlowNode for SOURCE | test.py:442:10:442:18 | ControlFlowNode for f() | Flow found |
397411
| test.py:449:10:449:34 | ControlFlowNode for second() | test.py:20:10:20:17 | ControlFlowNode for Str | test.py:449:10:449:34 | ControlFlowNode for second() | Flow found |
@@ -400,6 +414,8 @@ nodes
400414
| test.py:463:10:463:36 | ControlFlowNode for second() | test.py:463:30:463:35 | ControlFlowNode for SOURCE | test.py:463:10:463:36 | ControlFlowNode for second() | Flow found |
401415
| test.py:482:10:482:39 | ControlFlowNode for f_extra_pos() | test.py:20:10:20:17 | ControlFlowNode for Str | test.py:482:10:482:39 | ControlFlowNode for f_extra_pos() | Flow found |
402416
| test.py:482:10:482:39 | ControlFlowNode for f_extra_pos() | test.py:482:33:482:38 | ControlFlowNode for SOURCE | test.py:482:10:482:39 | ControlFlowNode for f_extra_pos() | Flow found |
417+
| test.py:487:10:487:45 | ControlFlowNode for f_extra_keyword() | test.py:20:10:20:17 | ControlFlowNode for Str | test.py:487:10:487:45 | ControlFlowNode for f_extra_keyword() | Flow found |
418+
| test.py:487:10:487:45 | ControlFlowNode for f_extra_keyword() | test.py:487:39:487:44 | ControlFlowNode for SOURCE | test.py:487:10:487:45 | ControlFlowNode for f_extra_keyword() | Flow found |
403419
| test.py:501:10:501:10 | ControlFlowNode for a | test.py:20:10:20:17 | ControlFlowNode for Str | test.py:501:10:501:10 | ControlFlowNode for a | Flow found |
404420
| test.py:501:10:501:10 | ControlFlowNode for a | test.py:499:9:499:14 | ControlFlowNode for SOURCE | test.py:501:10:501:10 | ControlFlowNode for a | Flow found |
405421
| test.py:506:10:506:10 | ControlFlowNode for b | test.py:20:10:20:17 | ControlFlowNode for Str | test.py:506:10:506:10 | ControlFlowNode for b | Flow found |

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ def f_extra_keyword(a, **b):
394394

395395

396396
def test_call_extra_keyword():
397-
SINK(f_extra_keyword(NONSOURCE, b=SOURCE)) # Flow missing
397+
SINK(f_extra_keyword(NONSOURCE, b=SOURCE))
398398

399399

400400
# return the name of the first extra keyword argument
@@ -484,7 +484,7 @@ def test_lambda_extra_pos():
484484

485485
def test_lambda_extra_keyword():
486486
f_extra_keyword = lambda a, **b: b["b"]
487-
SINK(f_extra_keyword(NONSOURCE, b=SOURCE)) # Flow missing
487+
SINK(f_extra_keyword(NONSOURCE, b=SOURCE))
488488

489489

490490
# call the function with our source as the name of the keyword arguemnt

0 commit comments

Comments
 (0)