Python: Support unpacking of keyword arguments.

This commit is contained in:
Rasmus Lerchedahl Petersen
2020-09-29 15:36:38 +02:00
parent e02cfbf6b0
commit 30d048f9d4
3 changed files with 43 additions and 2 deletions

View File

@@ -294,6 +294,12 @@ module ArgumentPassing {
n = -2 and
result = TKwOverflowNode(call, callable)
)
or
// argument unpacked from dict
exists(string name |
call_unpacks(call, callable, name, n) and
result = TKwUnpacked(call, callable, name)
)
)
}
@@ -320,6 +326,22 @@ module ArgumentPassing {
result = call.getArgByName(key)
)
}
/**
* Holds if `call` unpacks a dictionary argument in order to pass it via `name`.
* It will then be passed to the `n`th parameter of `callable`.
*/
predicate call_unpacks(CallNode call, CallableValue callable, string name, int n) {
exists(Function f |
f = callable.getScope() and
not exists(call.getArg(n)) and // no positional arguement available
name = f.getArgName(n) and
not exists(call.getArgByName(name)) and // no keyword argument available
n >= 0 and
n < f.getPositionalParameterCount() + f.getKeywordOnlyParameterCount() and
exists(call.getNode().getKwargs()) // dict argument available
)
}
}
import ArgumentPassing
@@ -760,6 +782,8 @@ predicate readStep(Node nodeFrom, Content c, Node nodeTo) {
comprehensionReadStep(nodeFrom, c, nodeTo)
or
attributeReadStep(nodeFrom, c, nodeTo)
or
kwUnpackReadStep(nodeFrom, c, nodeTo)
}
/** Data flows from a sequence to a subscript of the sequence. */
@@ -864,6 +888,19 @@ predicate attributeReadStep(CfgNode nodeFrom, AttributeContent c, CfgNode nodeTo
)
}
/**
* Holds if `nodeFrom` is a dictionary argument being unpacked and `nodeTo` is the
* synthezised unpacked argument with the name indicated by `c`.
*/
predicate kwUnpackReadStep(CfgNode nodeFrom, DictionaryElementContent c, Node nodeTo) {
exists(CallNode call, CallableValue callable, string name, int n |
call_unpacks(call, callable, name, n) and
nodeFrom.asCfgNode() = call.getNode().getKwargs().getAFlowNode() and
nodeTo = TKwUnpacked(call, callable, name) and
name = c.getKey()
)
}
/**
* Holds if values stored inside content `c` are cleared at node `n`. For example,
* any value stored inside `f` is cleared at the pre-update node associated with `x`

View File

@@ -36,6 +36,10 @@ newtype TNode =
/** A node representing the overflow keyword arguments to a call. */
TKwOverflowNode(CallNode call, CallableValue callable) {
exists(getKeywordOverflowArg(call, callable, _))
} or
/** A node representing an unpacked element of a dictionary argument. */
TKwUnpacked(CallNode call, CallableValue callable, string name) {
call_unpacks(call, callable, name, _)
}
/**

View File

@@ -378,7 +378,7 @@ def test_call_unpack_iterable():
def test_call_unpack_mapping():
SINK(second(NONSOURCE, **{"b": SOURCE})) # Flow missing
SINK(second(NONSOURCE, **{"b": SOURCE}))
def f_extra_pos(a, *b):
@@ -474,7 +474,7 @@ def test_lambda_unpack_mapping():
def second(a, b):
return b
SINK(second(NONSOURCE, **{"b": SOURCE})) # Flow missing
SINK(second(NONSOURCE, **{"b": SOURCE}))
def test_lambda_extra_pos():