mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
Python: Add basic support for **kwargs
For now this is JUST from `**kwargs` in arguments, to `**kwargs` parameters, and this part is based on field-flow Note that dataflow-library complains about missing post update nodes for these. This needs to be ignored, since post update nodes for `**kwargs` arguments doesn't make sense, it's not possible to alter the dictionary inside the method.
This commit is contained in:
@@ -411,6 +411,12 @@ class CallNode extends ControlFlowNode {
|
||||
result.getNode() = this.getNode().getStarArg() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
}
|
||||
|
||||
/** Gets a dictionary (**) argument of this call, if any. */
|
||||
ControlFlowNode getKwargs() {
|
||||
result.getNode() = this.getNode().getKwargs() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
}
|
||||
}
|
||||
|
||||
/** A control flow corresponding to an attribute expression, such as `value.attr` */
|
||||
|
||||
@@ -41,7 +41,8 @@ newtype TParameterPosition =
|
||||
/** Used for `self` in methods, and `cls` in classmethods. */
|
||||
TSelfParameterPosition() or
|
||||
TPositionalParameterPosition(int pos) { pos = any(Parameter p).getPosition() } or
|
||||
TKeywordParameterPosition(string name) { name = any(Parameter p).getName() }
|
||||
TKeywordParameterPosition(string name) { name = any(Parameter p).getName() } or
|
||||
TDictSplatParameterPosition()
|
||||
|
||||
/** A parameter position. */
|
||||
class ParameterPosition extends TParameterPosition {
|
||||
@@ -54,6 +55,9 @@ class ParameterPosition extends TParameterPosition {
|
||||
/** Holds if this position represents a keyword parameter named `name`. */
|
||||
predicate isKeyword(string name) { this = TKeywordParameterPosition(name) }
|
||||
|
||||
/** Holds if this position represents a `**kwargs` parameter. */
|
||||
predicate isDictSplat() { this = TDictSplatParameterPosition() }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
@@ -61,6 +65,8 @@ class ParameterPosition extends TParameterPosition {
|
||||
exists(int index | this.isPositional(index) and result = "position " + index)
|
||||
or
|
||||
exists(string name | this.isKeyword(name) and result = "keyword " + name)
|
||||
or
|
||||
this.isDictSplat() and result = "**"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +74,8 @@ newtype TArgumentPosition =
|
||||
/** Used for `self` in methods, and `cls` in classmethods. */
|
||||
TSelfArgumentPosition() or
|
||||
TPositionalArgumentPosition(int pos) { exists(any(CallNode c).getArg(pos)) } or
|
||||
TKeywordArgumentPosition(string name) { exists(any(CallNode c).getArgByName(name)) }
|
||||
TKeywordArgumentPosition(string name) { exists(any(CallNode c).getArgByName(name)) } or
|
||||
TDictSplatArgumentPosition()
|
||||
|
||||
/** An argument position. */
|
||||
class ArgumentPosition extends TArgumentPosition {
|
||||
@@ -81,6 +88,9 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
/** Holds if this position represents a keyword argument named `name`. */
|
||||
predicate isKeyword(string name) { this = TKeywordArgumentPosition(name) }
|
||||
|
||||
/** Holds if this position represents a `**kwargs` argument. */
|
||||
predicate isDictSplat() { this = TDictSplatArgumentPosition() }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
@@ -88,6 +98,8 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
exists(int pos | this.isPositional(pos) and result = "position " + pos)
|
||||
or
|
||||
exists(string name | this.isKeyword(name) and result = "keyword " + name)
|
||||
or
|
||||
this.isDictSplat() and result = "**"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +111,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
exists(int index | ppos.isPositional(index) and apos.isPositional(index))
|
||||
or
|
||||
exists(string name | ppos.isKeyword(name) and apos.isKeyword(name))
|
||||
or
|
||||
ppos.isDictSplat() and apos.isDictSplat()
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -183,6 +197,8 @@ abstract class DataFlowFunction extends DataFlowCallable, TFunction {
|
||||
)
|
||||
or
|
||||
exists(string name | ppos.isKeyword(name) | result.getParameter() = func.getArgByName(name))
|
||||
or
|
||||
ppos.isDictSplat() and result.getParameter() = func.getKwarg()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,6 +909,8 @@ private predicate normalCallArg(CallNode call, Node arg, ArgumentPosition apos)
|
||||
apos.isKeyword(name) and
|
||||
arg.asCfgNode() = call.getArgByName(name)
|
||||
)
|
||||
or
|
||||
apos.isDictSplat() and arg.asCfgNode() = call.getKwargs()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,7 +72,7 @@ def argument_passing(
|
||||
|
||||
@expects(7)
|
||||
def test_argument_passing1():
|
||||
argument_passing(arg1, *(arg2, arg3, arg4), e=arg5, **{"f": arg6, "g": arg7}) #$ arg1 arg5 MISSING: arg2 arg3 arg4 arg6 arg7
|
||||
argument_passing(arg1, *(arg2, arg3, arg4), e=arg5, **{"f": arg6, "g": arg7}) #$ arg1 arg5 arg7 func=argument_passing MISSING: arg2 arg3 arg4 arg6
|
||||
|
||||
|
||||
@expects(7)
|
||||
@@ -178,7 +178,7 @@ def only_kwargs(**kwargs):
|
||||
|
||||
@expects(3)
|
||||
def test_kwargs():
|
||||
args = {"a": arg1, "b": arg2, "c": "safe"} # $ MISSING: arg1 arg2 func=only_kwargs
|
||||
args = {"a": arg1, "b": arg2, "c": "safe"} # $ arg1 arg2 func=only_kwargs
|
||||
only_kwargs(**args)
|
||||
|
||||
|
||||
@@ -195,8 +195,8 @@ def mixed(a, **kwargs):
|
||||
def test_mixed():
|
||||
mixed(a=arg1, b=arg2, c="safe") # $ arg1 MISSING: arg2
|
||||
|
||||
args = {"b": arg2, "c": "safe"} # $ MISSING: arg2 func=mixed
|
||||
args = {"b": arg2, "c": "safe"} # $ arg2 func=mixed
|
||||
mixed(a=arg1, **args) # $ arg1
|
||||
|
||||
args = {"a": arg1, "b": arg2, "c": "safe"} # $ MISSING: arg2 func=mixed MISSING: arg1
|
||||
args = {"a": arg1, "b": arg2, "c": "safe"} # $ bad1="arg1" arg2 func=mixed
|
||||
mixed(**args)
|
||||
|
||||
@@ -17,5 +17,20 @@ uniquePostUpdate
|
||||
postIsInSameCallable
|
||||
reverseRead
|
||||
argHasPostUpdate
|
||||
| argumentPassing.py:75:59:75:80 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| argumentPassing.py:105:35:105:45 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| argumentPassing.py:106:29:106:39 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| argumentPassing.py:106:44:106:54 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| argumentPassing.py:106:59:106:69 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| argumentPassing.py:120:30:120:40 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| argumentPassing.py:182:19:182:22 | ControlFlowNode for args | ArgumentNode is missing PostUpdateNode. |
|
||||
| argumentPassing.py:196:21:196:24 | ControlFlowNode for args | ArgumentNode is missing PostUpdateNode. |
|
||||
| argumentPassing.py:199:13:199:16 | ControlFlowNode for args | ArgumentNode is missing PostUpdateNode. |
|
||||
| file:///home/rasmus/.pyenv/versions/3.9.5/lib/python3.9/functools.py:400:58:400:70 | ControlFlowNode for Attribute | ArgumentNode is missing PostUpdateNode. |
|
||||
| test.py:396:30:396:42 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| test.py:422:33:422:46 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| test.py:512:30:512:42 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| test.py:529:33:529:46 | ControlFlowNode for Dict | ArgumentNode is missing PostUpdateNode. |
|
||||
| test.py:838:17:838:18 | ControlFlowNode for dd | ArgumentNode is missing PostUpdateNode. |
|
||||
postWithInFlow
|
||||
viableImplInCallContextTooLarge
|
||||
|
||||
Reference in New Issue
Block a user