Skip to content

Commit 5d66c48

Browse files
committed
Python: IPA type for arguemnt mappings
Not sure how arg2 in line 118 is achieved
1 parent 05b7447 commit 5d66c48

File tree

5 files changed

+72
-31
lines changed

5 files changed

+72
-31
lines changed

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

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ private Node update(Node node) {
261261
* When a call contains an iterable unpacking argument, such as `func(*args)`, it is expanded into positional arguments.
262262
*
263263
* CURRENTLY NOT SUPPORTED:
264-
* When a call contains an iterable unpacking argument, such as `func(*args)`, and the callee contains a starred argument, any extra
264+
* If a call contains an iterable unpacking argument, such as `func(*args)`, and the callee contains a starred argument, any extra
265265
* positional arguments are passed to the starred argument.
266266
*
267267
* When a call contains keyword arguments that do not correspond to keyword parameters, these
@@ -313,7 +313,7 @@ private Node update(Node node) {
313313
* `y`. There is a dataflow step from `**{"y": 1, "a": 3}` to `[**d]` to transfer the content and
314314
* a clearing of content at key `y` for node `[**d]`, since that value has been unpacked.
315315
*/
316-
module ArgumentPassing {
316+
private module ArgumentPassing {
317317
/**
318318
* Holds if `call` represents a `DataFlowCall` to a `DataFlowCallable` represented by `callable`.
319319
*
@@ -354,23 +354,57 @@ module ArgumentPassing {
354354
)
355355
}
356356

357+
/**
358+
* A type representing a mapping from argument indices to parameter indices.
359+
* We currently use two mappings: NoShift, the identity, used for ordinary
360+
* function calls, and ShiftOne which is used for calls where an extra argument
361+
* is inserted. These include method calls, constructor calls and class calls.
362+
* In these calls, the argument at index `n` is mapped to the parameter at position `n+1`.
363+
*/
364+
newtype TArgParamMapping =
365+
TNoShift() or
366+
TShiftOne()
367+
368+
/** A mapping used for parameter passing. */
369+
abstract class ArgParamMapping extends TArgParamMapping {
370+
bindingset[result]
371+
abstract int getArgN(int paramN);
372+
373+
string toString() { none() }
374+
}
375+
376+
/** A mapping that passes argument `n` to parameter `n`. */
377+
class NoShift extends ArgParamMapping, TNoShift {
378+
NoShift() { this = TNoShift() }
379+
380+
bindingset[result]
381+
override int getArgN(int paramN) { result = paramN }
382+
}
383+
384+
/** A mapping that passes argument `n` to parameter `n+1`. */
385+
class ShiftOne extends ArgParamMapping, TShiftOne {
386+
ShiftOne() { this = TShiftOne() }
387+
388+
bindingset[result]
389+
override int getArgN(int paramN) { result = paramN - 1 }
390+
}
391+
357392
/**
358393
* Gets the node representing the argument to `call` that is passed to the parameter at
359394
* (zero-based) index `paramN` in `callable`. If this is a positional argument, it must appear
360-
* at index `argN` in `call`.
395+
* at index `mapping.getArgN(paramN)` in `call`.
361396
*
362-
* `argN` will differ from `paramN` for method- or constructor calls, where the first parameter
397+
* `mapping.getArgN(paramN)` will differ from `paramN` for method- or constructor calls, where the first parameter
363398
* is `self` and the first positional argument is passed to the second positional parameter.
364399
* Similarly for classmethod calls, where the first parameter is `cls`.
365400
*
366401
* NOT SUPPORTED: Keyword-only parameters.
367402
*/
368-
Node getArg(CallNode call, int argN, CallableValue callable, int paramN) {
403+
Node getArg(CallNode call, ArgParamMapping mapping, CallableValue callable, int paramN) {
369404
connects(call, callable) and
370-
paramN - argN in [0, 1] and // constrain for now to limit the size of the predicate; we only use it to insert one argument (self).
371405
(
372406
// positional argument
373-
result = TCfgNode(call.getArg(argN))
407+
result = TCfgNode(call.getArg(mapping.getArgN(paramN)))
374408
or
375409
// keyword argument
376410
// TODO: Since `getArgName` have no results for keyword-only parameters,
@@ -393,7 +427,7 @@ module ArgumentPassing {
393427
or
394428
// argument unpacked from dict
395429
exists(string name |
396-
call_unpacks(call, argN, callable, name, paramN) and
430+
call_unpacks(call, mapping, callable, name, paramN) and
397431
result = TKwUnpacked(call, callable, name)
398432
)
399433
)
@@ -425,20 +459,21 @@ module ArgumentPassing {
425459

426460
/**
427461
* Holds if `call` unpacks a dictionary argument in order to pass it via `name`.
428-
* It will then be passed to the `n`th parameter of `callable`.
462+
* It will then be passed to the parameter of `callable` at index `paramN`.
429463
*/
430-
predicate call_unpacks(CallNode call, int argNr, CallableValue callable, string name, int n) {
464+
predicate call_unpacks(
465+
CallNode call, ArgParamMapping mapping, CallableValue callable, string name, int paramN
466+
) {
431467
connects(call, callable) and
432-
n - argNr in [0, 1] and
433468
exists(Function f |
434469
f = callable.getScope() and
435-
not exists(call.getArg(argNr)) and // no positional arguement available
436-
name = f.getArgName(n) and
470+
not exists(call.getArg(mapping.getArgN(paramN))) and // no positional argument available
471+
name = f.getArgName(paramN) and
437472
// not exists(call.getArgByName(name)) and // only matches keyword arguments not preceded by **
438473
// TODO: make the below logic respect control flow splitting (by not going to the AST).
439474
not call.getNode().getANamedArg().(Keyword).getArg() = name and // no keyword argument available
440-
n >= 0 and
441-
n < f.getPositionalParameterCount() + f.getKeywordOnlyParameterCount() and
475+
paramN >= 0 and
476+
paramN < f.getPositionalParameterCount() + f.getKeywordOnlyParameterCount() and
442477
exists(call.getNode().getKwargs()) // dict argument available
443478
)
444479
}
@@ -581,7 +616,7 @@ class FunctionCall extends DataFlowCall, TFunctionCall {
581616

582617
override string toString() { result = call.toString() }
583618

584-
override Node getArg(int n) { result = getArg(call, n, callable.getCallableValue(), n) }
619+
override Node getArg(int n) { result = getArg(call, TNoShift(), callable.getCallableValue(), n) }
585620

586621
override ControlFlowNode getNode() { result = call }
587622

@@ -608,7 +643,7 @@ class MethodCall extends DataFlowCall, TMethodCall {
608643
override string toString() { result = call.toString() }
609644

610645
override Node getArg(int n) {
611-
n > 0 and result = getArg(call, n - 1, this.getCallableValue(), n)
646+
n > 0 and result = getArg(call, TShiftOne(), this.getCallableValue(), n)
612647
or
613648
n = 0 and result = TCfgNode(call.getFunction().(AttrNode).getObject())
614649
}
@@ -640,7 +675,7 @@ class ClassCall extends DataFlowCall, TClassCall {
640675
override string toString() { result = call.toString() }
641676

642677
override Node getArg(int n) {
643-
n > 0 and result = getArg(call, n - 1, this.getCallableValue(), n)
678+
n > 0 and result = getArg(call, TShiftOne(), this.getCallableValue(), n)
644679
or
645680
n = 0 and result = TSyntheticPreUpdateNode(TCfgNode(call))
646681
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ def test_multiple_kw_args():
117117
with_multiple_kw_args(b=arg2, c=arg3, a=arg1)
118118
with_multiple_kw_args(arg1, *(arg2,), arg3)
119119
with_multiple_kw_args(arg1, **{"c": arg3}, b=arg2)
120+
with_multiple_kw_args(**{"b": arg2}, **{"c": arg3}, **{"a": arg1})
120121

121122

122123
def with_default_arguments(a=arg1, b=arg2, c=arg3):

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66
| argumentPassing.py:117:45:117:48 | ControlFlowNode for arg1 | argumentPassing.py:110:11:110:11 | ControlFlowNode for a |
77
| argumentPassing.py:118:27:118:30 | ControlFlowNode for arg1 | argumentPassing.py:110:11:110:11 | ControlFlowNode for a |
88
| argumentPassing.py:119:27:119:30 | ControlFlowNode for arg1 | argumentPassing.py:110:11:110:11 | ControlFlowNode for a |
9-
| argumentPassing.py:131:28:131:31 | ControlFlowNode for arg1 | argumentPassing.py:123:11:123:11 | ControlFlowNode for a |
10-
| argumentPassing.py:159:46:159:49 | ControlFlowNode for arg1 | argumentPassing.py:138:11:138:13 | ControlFlowNode for foo |
11-
| argumentPassing.py:167:14:167:17 | ControlFlowNode for arg1 | argumentPassing.py:165:15:165:15 | ControlFlowNode for a |
12-
| argumentPassing.py:174:19:174:22 | ControlFlowNode for arg1 | argumentPassing.py:172:15:172:15 | ControlFlowNode for a |
13-
| argumentPassing.py:182:15:182:18 | ControlFlowNode for arg1 | argumentPassing.py:180:19:180:22 | ControlFlowNode for Subscript |
14-
| argumentPassing.py:189:13:189:16 | ControlFlowNode for arg1 | argumentPassing.py:187:15:187:15 | ControlFlowNode for a |
15-
| argumentPassing.py:196:16:196:19 | ControlFlowNode for arg1 | argumentPassing.py:194:15:194:15 | ControlFlowNode for a |
16-
| argumentPassing.py:203:15:203:18 | ControlFlowNode for arg1 | argumentPassing.py:201:15:201:15 | ControlFlowNode for a |
17-
| argumentPassing.py:210:23:210:26 | ControlFlowNode for arg1 | argumentPassing.py:208:15:208:20 | ControlFlowNode for Subscript |
9+
| argumentPassing.py:120:65:120:68 | ControlFlowNode for arg1 | argumentPassing.py:110:11:110:11 | ControlFlowNode for a |
10+
| argumentPassing.py:132:28:132:31 | ControlFlowNode for arg1 | argumentPassing.py:124:11:124:11 | ControlFlowNode for a |
11+
| argumentPassing.py:160:46:160:49 | ControlFlowNode for arg1 | argumentPassing.py:139:11:139:13 | ControlFlowNode for foo |
12+
| argumentPassing.py:168:14:168:17 | ControlFlowNode for arg1 | argumentPassing.py:166:15:166:15 | ControlFlowNode for a |
13+
| argumentPassing.py:175:19:175:22 | ControlFlowNode for arg1 | argumentPassing.py:173:15:173:15 | ControlFlowNode for a |
14+
| argumentPassing.py:183:15:183:18 | ControlFlowNode for arg1 | argumentPassing.py:181:19:181:22 | ControlFlowNode for Subscript |
15+
| argumentPassing.py:190:13:190:16 | ControlFlowNode for arg1 | argumentPassing.py:188:15:188:15 | ControlFlowNode for a |
16+
| argumentPassing.py:197:16:197:19 | ControlFlowNode for arg1 | argumentPassing.py:195:15:195:15 | ControlFlowNode for a |
17+
| argumentPassing.py:204:15:204:18 | ControlFlowNode for arg1 | argumentPassing.py:202:15:202:15 | ControlFlowNode for a |
18+
| argumentPassing.py:211:23:211:26 | ControlFlowNode for arg1 | argumentPassing.py:209:15:209:20 | ControlFlowNode for Subscript |
1819
| classes.py:563:5:563:16 | SSA variable with_getitem | classes.py:557:15:557:18 | ControlFlowNode for self |
1920
| classes.py:578:5:578:16 | SSA variable with_setitem | classes.py:573:15:573:18 | ControlFlowNode for self |
2021
| 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/argumentRouting2.expected

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
| argumentPassing.py:104:25:104:28 | ControlFlowNode for arg2 | argumentPassing.py:99:11:99:11 | ControlFlowNode for b |
33
| argumentPassing.py:105:27:105:30 | ControlFlowNode for arg2 | argumentPassing.py:99:11:99:11 | ControlFlowNode for b |
44
| argumentPassing.py:117:29:117:32 | ControlFlowNode for arg2 | argumentPassing.py:111:11:111:11 | ControlFlowNode for b |
5-
| argumentPassing.py:132:30:132:33 | ControlFlowNode for arg2 | argumentPassing.py:124:11:124:11 | ControlFlowNode for b |
6-
| argumentPassing.py:159:36:159:39 | ControlFlowNode for arg2 | argumentPassing.py:145:11:145:13 | ControlFlowNode for bar |
5+
| argumentPassing.py:118:35:118:38 | ControlFlowNode for arg2 | argumentPassing.py:111:11:111:11 | ControlFlowNode for b |
6+
| argumentPassing.py:119:50:119:53 | ControlFlowNode for arg2 | argumentPassing.py:111:11:111:11 | ControlFlowNode for b |
7+
| argumentPassing.py:120:35:120:38 | ControlFlowNode for arg2 | argumentPassing.py:111:11:111:11 | ControlFlowNode for b |
8+
| argumentPassing.py:133:30:133:33 | ControlFlowNode for arg2 | argumentPassing.py:125:11:125:11 | ControlFlowNode for b |
9+
| argumentPassing.py:160:36:160:39 | ControlFlowNode for arg2 | argumentPassing.py:146:11:146:13 | ControlFlowNode for bar |
710
| classes.py:565:18:565:21 | ControlFlowNode for arg2 | classes.py:556:15:556:17 | ControlFlowNode for key |
811
| classes.py:581:18:581:21 | ControlFlowNode for arg2 | classes.py:572:15:572:17 | ControlFlowNode for key |
912
| classes.py:595:22:595:25 | ControlFlowNode for arg2 | classes.py:587:15:587:17 | ControlFlowNode for key |

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
| argumentPassing.py:117:37:117:40 | ControlFlowNode for arg3 | argumentPassing.py:112:11:112:11 | ControlFlowNode for c |
33
| argumentPassing.py:118:43:118:46 | ControlFlowNode for arg3 | argumentPassing.py:112:11:112:11 | ControlFlowNode for c |
44
| argumentPassing.py:119:41:119:44 | ControlFlowNode for arg3 | argumentPassing.py:112:11:112:11 | ControlFlowNode for c |
5-
| argumentPassing.py:133:36:133:39 | ControlFlowNode for arg3 | argumentPassing.py:125:11:125:11 | ControlFlowNode for c |
6-
| argumentPassing.py:159:26:159:29 | ControlFlowNode for arg3 | argumentPassing.py:154:11:154:13 | ControlFlowNode for baz |
5+
| argumentPassing.py:120:50:120:53 | ControlFlowNode for arg3 | argumentPassing.py:112:11:112:11 | ControlFlowNode for c |
6+
| argumentPassing.py:134:36:134:39 | ControlFlowNode for arg3 | argumentPassing.py:126:11:126:11 | ControlFlowNode for c |
7+
| argumentPassing.py:160:26:160:29 | ControlFlowNode for arg3 | argumentPassing.py:155:11:155:13 | ControlFlowNode for baz |
78
| classes.py:581:26:581:29 | ControlFlowNode for arg3 | classes.py:571:15:571:19 | ControlFlowNode for value |

0 commit comments

Comments
 (0)