Python: Support flow to **kwargs param from keyword arg

This commit is contained in:
Rasmus Wriedt Larsen
2022-09-09 13:59:54 +02:00
parent 503ad544e9
commit 215a03d948
5 changed files with 43 additions and 6 deletions

View File

@@ -910,7 +910,12 @@ private predicate normalCallArg(CallNode call, Node arg, ArgumentPosition apos)
arg.asCfgNode() = call.getArgByName(name)
)
or
apos.isDictSplat() and arg.asCfgNode() = call.getKwargs()
apos.isDictSplat() and
(
arg.asCfgNode() = call.getKwargs()
or
arg = TSynthDictSplatArgumentNode(call)
)
}
/**

View File

@@ -54,6 +54,33 @@ class SyntheticPreUpdateNode extends Node, TSyntheticPreUpdateNode {
override Location getLocation() { result = node.getLocation() }
}
/**
* A (synthetic) data-flow node that represents all keyword arguments, as if they had
* been passed in a `**kwargs` argument.
*/
class SynthDictSplatArgumentNode extends Node, TSynthDictSplatArgumentNode {
CallNode node;
SynthDictSplatArgumentNode() { this = TSynthDictSplatArgumentNode(node) }
override string toString() { result = "SynthDictSplatArgumentNode" }
override Scope getScope() { result = node.getScope() }
override Location getLocation() { result = node.getLocation() }
}
private predicate synthDictSplatArgumentNodeStoreStep(
ArgumentNode nodeFrom, DictionaryElementContent c, SynthDictSplatArgumentNode nodeTo
) {
exists(string name, CallNode call, ArgumentPosition keywordPos |
nodeTo = TSynthDictSplatArgumentNode(call) and
getCallArg(call, _, _, nodeFrom, keywordPos) and
keywordPos.isKeyword(name) and
c.getKey() = name
)
}
/**
* Ensures that the a `**kwargs` parameter will not contain elements with names of
* keyword parameters.
@@ -426,6 +453,8 @@ predicate storeStep(Node nodeFrom, Content c, Node nodeTo) {
any(Orm::AdditionalOrmSteps es).storeStep(nodeFrom, c, nodeTo)
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(nodeFrom, c, nodeTo)
or
synthDictSplatArgumentNodeStoreStep(nodeFrom, c, nodeTo)
}
/**
@@ -752,6 +781,8 @@ predicate nodeIsHidden(Node n) {
n instanceof SummaryNode
or
n instanceof SummaryParameterNode
or
n instanceof SynthDictSplatArgumentNode
}
class LambdaCallKind = Unit;

View File

@@ -112,7 +112,8 @@ newtype TNode =
} or
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
}
} or
TSynthDictSplatArgumentNode(CallNode call) { exists(call.getArgByName(_)) }
class TParameterNode = TCfgNode or TSummaryParameterNode;

View File

@@ -168,7 +168,7 @@ def test_kw_doublestar():
def with_doublestar(**kwargs):
SINK1(kwargs["a"])
with_doublestar(a=arg1) #$ MISSING: arg1 func=test_kw_doublestar.with_doublestar
with_doublestar(a=arg1) #$ arg1 func=test_kw_doublestar.with_doublestar
def only_kwargs(**kwargs):
@@ -193,7 +193,7 @@ def mixed(a, **kwargs):
@expects(4*3)
def test_mixed():
mixed(a=arg1, b=arg2, c="safe") # $ arg1 MISSING: arg2
mixed(a=arg1, b=arg2, c="safe") # $ arg1 arg2
args = {"b": arg2, "c": "safe"} # $ arg2 func=mixed
mixed(a=arg1, **args) # $ arg1

View File

@@ -409,7 +409,7 @@ def f_extra_keyword(a, **b):
def test_call_extra_keyword():
SINK(f_extra_keyword(NONSOURCE, b=SOURCE)) #$ MISSING: flow="SOURCE -> f_extra_keyword(..)"
SINK(f_extra_keyword(NONSOURCE, b=SOURCE)) #$ flow="SOURCE -> f_extra_keyword(..)"
# return the name of the first extra keyword argument
@@ -519,7 +519,7 @@ def test_lambda_extra_pos():
def test_lambda_extra_keyword():
f_extra_keyword = lambda a, **b: b["b"]
SINK(f_extra_keyword(NONSOURCE, b=SOURCE)) #$ MISSING: flow="SOURCE -> f_extra_keyword(..)"
SINK(f_extra_keyword(NONSOURCE, b=SOURCE)) #$ flow="SOURCE -> f_extra_keyword(..)"
# call the function with our source as the name of the keyword argument