Skip to content

Commit bdda0f5

Browse files
committed
Python: Use new parameter position for synthetic **kwargs instead
We wanted to ensure that a callable did not have multiple parameters with same parameter position. Originally we fixed this with 02b3a1b (like Ruby). This commit reverts that and solves it by introducing a new parameter position instead.
1 parent a731a82 commit bdda0f5

File tree

2 files changed

+23
-50
lines changed

2 files changed

+23
-50
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,14 @@ newtype TParameterPosition =
6464
index = any(Parameter p).getPosition() + 1
6565
} or
6666
TSynthStarArgsElementParameterPosition(int index) { exists(TStarArgsParameterPosition(index)) } or
67-
TDictSplatParameterPosition()
67+
TDictSplatParameterPosition() or
68+
// To get flow from a **kwargs argument to a keyword parameter, we add a read-step
69+
// from a synthetic **kwargs parameter. We need this separate synthetic ParameterNode,
70+
// since we clear content of the normal **kwargs parameter for the names that
71+
// correspond to normal keyword parameters. Since we cannot re-use the same parameter
72+
// position for multiple parameter nodes in the same callable, we introduce this
73+
// synthetic parameter position.
74+
TSynthDictSplatParameterPosition()
6875

6976
/** A parameter position. */
7077
class ParameterPosition extends TParameterPosition {
@@ -92,6 +99,12 @@ class ParameterPosition extends TParameterPosition {
9299
/** Holds if this position represents a `**kwargs` parameter. */
93100
predicate isDictSplat() { this = TDictSplatParameterPosition() }
94101

102+
/**
103+
* Holds if this position represents a **synthetic** `**kwargs` parameter
104+
* (see comment for `TSynthDictSplatParameterPosition`).
105+
*/
106+
predicate isSynthDictSplat() { this = TSynthDictSplatParameterPosition() }
107+
95108
/** Gets a textual representation of this element. */
96109
string toString() {
97110
this.isSelf() and result = "self"
@@ -108,6 +121,8 @@ class ParameterPosition extends TParameterPosition {
108121
)
109122
or
110123
this.isDictSplat() and result = "**"
124+
or
125+
this.isSynthDictSplat() and result = "synthetic **"
111126
}
112127
}
113128

@@ -179,6 +194,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
179194
)
180195
or
181196
ppos.isDictSplat() and apos.isDictSplat()
197+
or
198+
ppos.isSynthDictSplat() and apos.isDictSplat()
182199
}
183200

184201
// =============================================================================
@@ -324,16 +341,9 @@ abstract class DataFlowFunction extends DataFlowCallable, TFunction {
324341
)
325342
or
326343
// `**kwargs`
327-
// since the dataflow library has the restriction that we can only have ONE result per
328-
// parameter position, if there is both a synthetic **kwargs and a real **kwargs
329-
// parameter, we only give the result for the synthetic, and add local flow from the
330-
// synthetic to the real. It might seem more natural to do it in the other
331-
// direction, but since we have a clearStep on the real **kwargs parameter, we would have that
332-
// content-clearing would also affect the synthetic parameter, which we don't want.
333-
ppos.isDictSplat() and
334-
if exists(func.getArgByName(_))
335-
then result = TSynthDictSplatParameterNode(this)
336-
else result.getParameter() = func.getKwarg()
344+
ppos.isDictSplat() and result.getParameter() = func.getKwarg()
345+
or
346+
ppos.isSynthDictSplat() and result = TSynthDictSplatParameterNode(this)
337347
}
338348
}
339349

@@ -1460,16 +1470,7 @@ class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
14601470
override Parameter getParameter() { none() }
14611471

14621472
override predicate isParameterOf(DataFlowCallable c, ParameterPosition ppos) {
1463-
sc = c.asLibraryCallable() and
1464-
ppos = pos and
1465-
// avoid overlap with `SynthDictSplatParameterNode`
1466-
not (
1467-
pos.isDictSplat() and
1468-
exists(ParameterPosition keywordPos |
1469-
FlowSummaryImpl::Private::summaryParameterNodeRange(sc, keywordPos) and
1470-
keywordPos.isKeyword(_)
1471-
)
1472-
)
1473+
sc = c.asLibraryCallable() and ppos = pos
14731474
}
14741475

14751476
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = sc }

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,7 @@ private predicate synthDictSplatArgumentNodeStoreStep(
181181
private predicate dictSplatParameterNodeClearStep(ParameterNode n, DictionaryElementContent c) {
182182
exists(DataFlowCallable callable, ParameterPosition dictSplatPos, ParameterPosition keywordPos |
183183
dictSplatPos.isDictSplat() and
184-
(
185-
n.getParameter() = callable.(DataFlowFunction).getScope().getKwarg()
186-
or
187-
n = TSummaryParameterNode(callable.asLibraryCallable(), dictSplatPos)
188-
) and
184+
n = callable.getParameter(dictSplatPos) and
189185
exists(callable.getParameter(keywordPos)) and
190186
keywordPos.isKeyword(c.getKey())
191187
)
@@ -236,28 +232,6 @@ class SynthDictSplatParameterNode extends ParameterNodeImpl, TSynthDictSplatPara
236232
override Parameter getParameter() { none() }
237233
}
238234

239-
/**
240-
* Flow step from the synthetic `**kwargs` parameter to the real `**kwargs` parameter.
241-
* Due to restriction in dataflow library, we can only give one of them as result for
242-
* `DataFlowCallable.getParameter`, so this is a workaround to ensure there is flow to
243-
* _both_ of them.
244-
*/
245-
private predicate dictSplatParameterNodeFlowStep(
246-
ParameterNodeImpl nodeFrom, ParameterNodeImpl nodeTo
247-
) {
248-
exists(DataFlowCallable callable |
249-
nodeFrom = TSynthDictSplatParameterNode(callable) and
250-
(
251-
nodeTo.getParameter() = callable.(DataFlowFunction).getScope().getKwarg()
252-
or
253-
exists(ParameterPosition pos |
254-
nodeTo = TSummaryParameterNode(callable.asLibraryCallable(), pos) and
255-
pos.isDictSplat()
256-
)
257-
)
258-
)
259-
}
260-
261235
/**
262236
* Reads from the synthetic **kwargs parameter to each keyword parameter.
263237
*/
@@ -403,8 +377,6 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
403377
simpleLocalFlowStepForTypetracking(nodeFrom, nodeTo)
404378
or
405379
summaryFlowSteps(nodeFrom, nodeTo)
406-
or
407-
dictSplatParameterNodeFlowStep(nodeFrom, nodeTo)
408380
}
409381

410382
/**

0 commit comments

Comments
 (0)