mirror of
https://github.com/github/codeql.git
synced 2026-05-03 04:39:29 +02:00
Python: Add basic support for *args
This commit is contained in:
@@ -42,6 +42,13 @@ newtype TParameterPosition =
|
||||
TSelfParameterPosition() or
|
||||
TPositionalParameterPosition(int pos) { pos = any(Parameter p).getPosition() } or
|
||||
TKeywordParameterPosition(string name) { name = any(Parameter p).getName() } or
|
||||
TStarArgsParameterPosition(int pos) {
|
||||
// since `.getPosition` does not work for `*args`, we need *args parameter positions
|
||||
// at index 1 larger than the largest positional parameter position (and 0 must be
|
||||
// included as well). This is a bit of an over-approximation.
|
||||
pos = 0 or
|
||||
pos = any(Parameter p).getPosition() + 1
|
||||
} or
|
||||
TDictSplatParameterPosition()
|
||||
|
||||
/** A parameter position. */
|
||||
@@ -55,6 +62,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 `*args` parameter at (0-based) `index`. */
|
||||
predicate isStarArgs(int index) { this = TStarArgsParameterPosition(index) }
|
||||
|
||||
/** Holds if this position represents a `**kwargs` parameter. */
|
||||
predicate isDictSplat() { this = TDictSplatParameterPosition() }
|
||||
|
||||
@@ -66,6 +76,8 @@ class ParameterPosition extends TParameterPosition {
|
||||
or
|
||||
exists(string name | this.isKeyword(name) and result = "keyword " + name)
|
||||
or
|
||||
exists(int index | this.isStarArgs(index) and result = "*args at " + index)
|
||||
or
|
||||
this.isDictSplat() and result = "**"
|
||||
}
|
||||
}
|
||||
@@ -75,6 +87,7 @@ newtype TArgumentPosition =
|
||||
TSelfArgumentPosition() or
|
||||
TPositionalArgumentPosition(int pos) { exists(any(CallNode c).getArg(pos)) } or
|
||||
TKeywordArgumentPosition(string name) { exists(any(CallNode c).getArgByName(name)) } or
|
||||
TStarArgsArgumentPosition(int pos) { exists(Call c | c.getPositionalArg(pos) instanceof Starred) } or
|
||||
TDictSplatArgumentPosition()
|
||||
|
||||
/** An argument position. */
|
||||
@@ -88,6 +101,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 `*args` argument at (0-based) `index`. */
|
||||
predicate isStarArgs(int index) { this = TStarArgsArgumentPosition(index) }
|
||||
|
||||
/** Holds if this position represents a `**kwargs` argument. */
|
||||
predicate isDictSplat() { this = TDictSplatArgumentPosition() }
|
||||
|
||||
@@ -99,6 +115,8 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
or
|
||||
exists(string name | this.isKeyword(name) and result = "keyword " + name)
|
||||
or
|
||||
exists(int index | this.isStarArgs(index) and result = "*args at " + index)
|
||||
or
|
||||
this.isDictSplat() and result = "**"
|
||||
}
|
||||
}
|
||||
@@ -112,6 +130,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
or
|
||||
exists(string name | ppos.isKeyword(name) and apos.isKeyword(name))
|
||||
or
|
||||
exists(int index | ppos.isStarArgs(index) and apos.isStarArgs(index))
|
||||
or
|
||||
ppos.isDictSplat() and apos.isDictSplat()
|
||||
}
|
||||
|
||||
@@ -198,6 +218,22 @@ abstract class DataFlowFunction extends DataFlowCallable, TFunction {
|
||||
or
|
||||
exists(string name | ppos.isKeyword(name) | result.getParameter() = func.getArgByName(name))
|
||||
or
|
||||
exists(int index |
|
||||
ppos.isStarArgs(index) and
|
||||
result.getParameter() = func.getVararg()
|
||||
|
|
||||
// a `*args` parameter comes after the last positional parameter. We need to take
|
||||
// self parameter into account, so for
|
||||
// `def func(foo, bar, *args)` it should be index 2 (1 + max-index == 1 + 1)
|
||||
// `class A: def func(self, foo, bar, *args)` it should be index 2 (1 + max-index - 1 == 1 + 2 - 1)
|
||||
index =
|
||||
1 + max(int positionalIndex | exists(func.getArg(positionalIndex)) | positionalIndex) -
|
||||
this.positionalOffset()
|
||||
or
|
||||
// no positional argument
|
||||
not exists(func.getArg(_)) and index = 0
|
||||
)
|
||||
or
|
||||
ppos.isDictSplat() and result.getParameter() = func.getKwarg()
|
||||
or
|
||||
ppos.isDictSplat() and result = TSynthDictSplatParameterNode(this)
|
||||
@@ -912,6 +948,12 @@ private predicate normalCallArg(CallNode call, Node arg, ArgumentPosition apos)
|
||||
arg.asCfgNode() = call.getArgByName(name)
|
||||
)
|
||||
or
|
||||
exists(int index |
|
||||
apos.isStarArgs(index) and
|
||||
arg.asCfgNode() = call.getStarArg() and
|
||||
call.getStarArg().getNode() = call.getNode().getPositionalArg(index).(Starred).getValue()
|
||||
)
|
||||
or
|
||||
apos.isDictSplat() and
|
||||
(
|
||||
arg.asCfgNode() = call.getKwargs()
|
||||
|
||||
@@ -6,6 +6,8 @@ import semmle.python.dataflow.new.internal.DataFlowImplConsistency::Consistency
|
||||
// `python/ql/consistency-queries`. For for now it resides here.
|
||||
private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
override predicate argHasPostUpdateExclude(ArgumentNode n) {
|
||||
exists(ArgumentPosition apos | n.argumentOf(_, apos) and apos.isStarArgs(_))
|
||||
or
|
||||
exists(ArgumentPosition apos | n.argumentOf(_, apos) and apos.isDictSplat())
|
||||
}
|
||||
|
||||
|
||||
@@ -214,8 +214,8 @@ def test_only_starargs():
|
||||
args = (arg2, "safe")
|
||||
starargs_only(arg1, *args) # $ MISSING: arg1 arg2
|
||||
|
||||
args = (arg1, arg2, "safe")
|
||||
starargs_only(*args) # $ MISSING: arg1 arg2
|
||||
args = (arg1, arg2, "safe") # $ arg1 arg2 func=starargs_only
|
||||
starargs_only(*args)
|
||||
|
||||
|
||||
def starargs_mixed(a, *args):
|
||||
@@ -227,8 +227,8 @@ def starargs_mixed(a, *args):
|
||||
def test_stararg_mixed():
|
||||
starargs_mixed(arg1, arg2, "safe") # $ arg1 MISSING: arg2
|
||||
|
||||
args = (arg2, "safe")
|
||||
starargs_mixed(arg1, *args) # $ arg1 MISSING: arg2
|
||||
args = (arg2, "safe") # $ arg2 func=starargs_mixed
|
||||
starargs_mixed(arg1, *args) # $ arg1
|
||||
|
||||
args = (arg1, arg2, "safe")
|
||||
starargs_mixed(*args) # $ MISSING: arg1 arg2
|
||||
|
||||
Reference in New Issue
Block a user