Skip to content

Commit b3a49ab

Browse files
authored
Merge pull request github#12467 from RasmusWL/kwargs-parameter-position-fixup
Python/Ruby: Use new parameter position for synthetic hash-splat instead
2 parents 720eed3 + 293f791 commit b3a49ab

File tree

5 files changed

+39
-85
lines changed

5 files changed

+39
-85
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
@@ -182,11 +182,7 @@ private predicate synthDictSplatArgumentNodeStoreStep(
182182
private predicate dictSplatParameterNodeClearStep(ParameterNode n, DictionaryElementContent c) {
183183
exists(DataFlowCallable callable, ParameterPosition dictSplatPos, ParameterPosition keywordPos |
184184
dictSplatPos.isDictSplat() and
185-
(
186-
n.getParameter() = callable.(DataFlowFunction).getScope().getKwarg()
187-
or
188-
n = TSummaryParameterNode(callable.asLibraryCallable(), dictSplatPos)
189-
) and
185+
n = callable.getParameter(dictSplatPos) and
190186
exists(callable.getParameter(keywordPos)) and
191187
keywordPos.isKeyword(c.getKey())
192188
)
@@ -237,28 +233,6 @@ class SynthDictSplatParameterNode extends ParameterNodeImpl, TSynthDictSplatPara
237233
override Parameter getParameter() { none() }
238234
}
239235

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

411383
/**

python/ql/test/experimental/dataflow/TestUtil/DataFlowConsistency.qll

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,6 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
2020
n instanceof SynthDictSplatParameterNode
2121
}
2222

23-
override predicate uniqueParameterNodeAtPositionExclude(
24-
DataFlowCallable c, ParameterPosition pos, Node p
25-
) {
26-
// TODO: This can be removed once we solve the overlap of dictionary splat parameters
27-
c.getParameter(pos) = p and
28-
pos.isDictSplat() and
29-
not exists(p.getLocation().getFile().getRelativePath())
30-
}
31-
3223
override predicate uniqueParameterNodePositionExclude(
3324
DataFlowCallable c, ParameterPosition pos, Node p
3425
) {

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,13 @@ private module Cached {
443443
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name)
444444
} or
445445
THashSplatParameterPosition() or
446+
// To get flow from a hash-splat argument to a keyword parameter, we add a read-step
447+
// from a synthetic hash-splat parameter. We need this separate synthetic ParameterNode,
448+
// since we clear content of the normal hash-splat parameter for the names that
449+
// correspond to normal keyword parameters. Since we cannot re-use the same parameter
450+
// position for multiple parameter nodes in the same callable, we introduce this
451+
// synthetic parameter position.
452+
TSynthHashSplatParameterPosition() or
446453
TSplatAllParameterPosition() or
447454
TAnyParameterPosition() or
448455
TAnyKeywordParameterPosition()
@@ -1249,6 +1256,8 @@ class ParameterPosition extends TParameterPosition {
12491256
/** Holds if this position represents a hash-splat parameter. */
12501257
predicate isHashSplat() { this = THashSplatParameterPosition() }
12511258

1259+
predicate isSynthHashSplat() { this = TSynthHashSplatParameterPosition() }
1260+
12521261
predicate isSplatAll() { this = TSplatAllParameterPosition() }
12531262

12541263
/**
@@ -1274,6 +1283,8 @@ class ParameterPosition extends TParameterPosition {
12741283
or
12751284
this.isHashSplat() and result = "**"
12761285
or
1286+
this.isSynthHashSplat() and result = "synthetic **"
1287+
or
12771288
this.isSplatAll() and result = "*"
12781289
or
12791290
this.isAny() and result = "any"
@@ -1356,6 +1367,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
13561367
or
13571368
ppos.isHashSplat() and apos.isHashSplat()
13581369
or
1370+
ppos.isSynthHashSplat() and apos.isHashSplat()
1371+
or
13591372
ppos.isSplatAll() and apos.isSplatAll()
13601373
or
13611374
ppos.isAny() and argumentPositionIsNotSelf(apos)

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -189,18 +189,6 @@ module LocalFlow {
189189
}
190190

191191
predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
192-
exists(DataFlowCallable c | nodeFrom = TSynthHashSplatParameterNode(c) |
193-
exists(HashSplatParameter p |
194-
p.getCallable() = c.asCallable() and
195-
nodeTo = TNormalParameterNode(p)
196-
)
197-
or
198-
exists(ParameterPosition pos |
199-
nodeTo = TSummaryParameterNode(c.asLibraryCallable(), pos) and
200-
pos.isHashSplat()
201-
)
202-
)
203-
or
204192
localSsaFlowStep(nodeFrom, nodeTo)
205193
or
206194
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::BlockArgumentCfgNode).getValue()
@@ -648,9 +636,7 @@ private module ParameterNodes {
648636
)
649637
or
650638
parameter = callable.getAParameter().(HashSplatParameter) and
651-
pos.isHashSplat() and
652-
// avoid overlap with `SynthHashSplatParameterNode`
653-
not callable.getAParameter() instanceof KeywordParameter
639+
pos.isHashSplat()
654640
or
655641
parameter = callable.getParameter(0).(SplatParameter) and
656642
pos.isSplatAll()
@@ -780,7 +766,7 @@ private module ParameterNodes {
780766
final override Parameter getParameter() { none() }
781767

782768
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
783-
c = callable and pos.isHashSplat()
769+
c = callable and pos.isSynthHashSplat()
784770
}
785771

786772
final override CfgScope getCfgScope() { result = callable.asCallable() }
@@ -802,16 +788,7 @@ private module ParameterNodes {
802788
override Parameter getParameter() { none() }
803789

804790
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
805-
sc = c.asLibraryCallable() and
806-
pos = pos_ and
807-
// avoid overlap with `SynthHashSplatParameterNode`
808-
not (
809-
pos.isHashSplat() and
810-
exists(ParameterPosition keywordPos |
811-
FlowSummaryImpl::Private::summaryParameterNodeRange(sc, keywordPos) and
812-
keywordPos.isKeyword(_)
813-
)
814-
)
791+
sc = c.asLibraryCallable() and pos = pos_
815792
}
816793

817794
override CfgScope getCfgScope() { none() }

0 commit comments

Comments
 (0)