Skip to content

Commit 3c86834

Browse files
committed
Ruby: Model more splat flow (alternative approach)
1 parent 9ccd8cd commit 3c86834

File tree

3 files changed

+84
-37
lines changed

3 files changed

+84
-37
lines changed

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -472,11 +472,7 @@ private module Cached {
472472
TSplatParameterPosition(int pos) {
473473
exists(Parameter p | p.getPosition() = pos and p instanceof SplatParameter)
474474
} or
475-
TSynthSplatParameterPosition(int pos) {
476-
// `pos` is the position of the splat _argument_ that is matched to the
477-
// `SynthSplatParameterNode` with this position.
478-
exists(ArgumentPosition a | a.isSplat(pos))
479-
} or
475+
TSynthSplatParameterPosition() or
480476
TSynthArgSplatParameterPosition() or
481477
TAnyParameterPosition() or
482478
TAnyKeywordParameterPosition()
@@ -1305,7 +1301,7 @@ class ParameterPosition extends TParameterPosition {
13051301

13061302
predicate isSynthHashSplat() { this = TSynthHashSplatParameterPosition() }
13071303

1308-
predicate isSynthSplat(int n) { this = TSynthSplatParameterPosition(n) }
1304+
predicate isSynthSplat() { this = TSynthSplatParameterPosition() }
13091305

13101306
// A fake position to indicate that this parameter node holds content from a synth arg splat node
13111307
predicate isSynthArgSplat() { this = TSynthArgSplatParameterPosition() }
@@ -1343,7 +1339,7 @@ class ParameterPosition extends TParameterPosition {
13431339
or
13441340
this.isAnyNamed() and result = "any-named"
13451341
or
1346-
exists(int pos | this.isSynthSplat(pos) and result = "synthetic * (position " + pos + ")")
1342+
this.isSynthSplat() and result = "synthetic *"
13471343
or
13481344
this.isSynthArgSplat() and result = "synthetic * (from *args)"
13491345
or
@@ -1446,7 +1442,7 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
14461442
or
14471443
ppos.isSplat(0) and apos.isSynthSplat()
14481444
or
1449-
exists(int n | ppos.isSynthSplat(n) and apos.isSplat(n))
1445+
ppos.isSynthSplat() and apos.isSynthSplat()
14501446
or
14511447
apos.isSynthSplat() and ppos.isSynthArgSplat()
14521448
or

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

Lines changed: 79 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -448,15 +448,14 @@ private module Cached {
448448
TSynthHashSplatParameterNode(DataFlowCallable c) {
449449
isParameterNode(_, c, any(ParameterPosition p | p.isKeyword(_)))
450450
} or
451-
TSynthSplatParameterNode(DataFlowCallable c, int n) {
451+
TSynthSplatParameterNode(DataFlowCallable c) {
452452
exists(c.asCallable()) and // exclude library callables
453-
isParameterNode(_, c, any(ParameterPosition p | p.isPositional(_))) and
453+
isParameterNode(_, c, any(ParameterPosition p | p.isPositional(_)))
454454
// `n` is the position of the splat argument that is matched to this node
455-
exists(ArgumentPosition pos | pos.isSplat(n))
456455
} or
457456
TSynthSplatArgParameterNode(DataFlowCallable c) {
458-
exists(c.asCallable()) and // exclude library callables
459-
isParameterNode(_, c, any(ParameterPosition p | p.isSplat(_)))
457+
exists(c.asCallable()) // exclude library callables
458+
// and isParameterNode(_, c, any(ParameterPosition p | p.isSplat(_)))
460459
} or
461460
TSynthSplatParameterElementNode(DataFlowCallable c, int n) {
462461
exists(c.asCallable()) and // exclude library callables
@@ -478,15 +477,17 @@ private module Cached {
478477
or
479478
c.getAnArgument() instanceof CfgNodes::ExprNodes::PairCfgNode
480479
} or
481-
TSynthSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) {
482-
exists(Argument arg, ArgumentPosition pos | pos.isPositional(_) | arg.isArgumentOf(c, pos)) and
483-
not exists(Argument arg, ArgumentPosition pos | pos.isSplat(_) | arg.isArgumentOf(c, pos))
480+
TSynthSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) or
481+
TSynthSplatArgumentElementNode(CfgNodes::ExprNodes::CallCfgNode c, int n) {
482+
n in [0 .. 10] and
483+
exists(Argument arg, ArgumentPosition pos | pos.isSplat(_) and arg.isArgumentOf(c, pos))
484484
} or
485485
TCaptureNode(VariableCapture::Flow::SynthesizedCaptureNode cn)
486486

487487
class TSourceParameterNode =
488488
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
489-
TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode;
489+
TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode or
490+
TSynthSplatArgumentElementNode;
490491

491492
cached
492493
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
@@ -1037,7 +1038,7 @@ private module ParameterNodes {
10371038
*
10381039
* This node stores the index of the splat argument it is matched to, which allows us to shift
10391040
* indices correctly when adding read steps. Without this, in the example above we would erroneously
1040-
* get a read step to `x` and index 0 and `y` and index 1 etc.
1041+
* get a read step to `x` at index 0 and `y` at index 1 etc.
10411042
*
10421043
* We don't yet correctly handle cases where a positional argument follows the splat argument, e.g. in
10431044
* ```rb
@@ -1046,10 +1047,8 @@ private module ParameterNodes {
10461047
*/
10471048
class SynthSplatParameterNode extends ParameterNodeImpl, TSynthSplatParameterNode {
10481049
private DataFlowCallable callable;
1049-
// The position of the splat argument that is matched to this node
1050-
private int n;
10511050

1052-
SynthSplatParameterNode() { this = TSynthSplatParameterNode(callable, n) }
1051+
SynthSplatParameterNode() { this = TSynthSplatParameterNode(callable) }
10531052

10541053
/**
10551054
* Gets a parameter which will contain the value given by `c`.
@@ -1064,10 +1063,10 @@ private module ParameterNodes {
10641063
* then `getAParameter(element 0) = y`.
10651064
*/
10661065
ParameterNode getAParameter(ContentSet c) {
1067-
exists(int m |
1068-
isParameterNode(result, callable, (any(ParameterPosition p | p.isPositional(m)))) and
1066+
exists(int n |
1067+
isParameterNode(result, callable, (any(ParameterPosition p | p.isPositional(n)))) and
10691068
(
1070-
c = getPositionalContent(m - n)
1069+
c = getPositionalContent(n)
10711070
or
10721071
c.isSingleton(TUnknownElementContent())
10731072
)
@@ -1077,7 +1076,7 @@ private module ParameterNodes {
10771076
final override Parameter getParameter() { none() }
10781077

10791078
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
1080-
c = callable and pos.isSynthSplat(n)
1079+
c = callable and pos.isSynthSplat()
10811080
}
10821081

10831082
final override CfgScope getCfgScope() { result = callable.asCallable() }
@@ -1132,12 +1131,7 @@ private module ParameterNodes {
11321131

11331132
int getStorePosition() { result = pos }
11341133

1135-
int getReadPosition() {
1136-
exists(int splatPos |
1137-
exists(this.getSplatParameterNode(splatPos)) and
1138-
result = pos + splatPos
1139-
)
1140-
}
1134+
int getReadPosition() { result = pos }
11411135

11421136
final override CfgScope getCfgScope() { result = callable.asCallable() }
11431137

@@ -1322,6 +1316,23 @@ module ArgumentNodes {
13221316

13231317
override string toStringImpl() { result = "*" }
13241318
}
1319+
1320+
class SynthSplatArgumentElementNode extends NodeImpl, TSynthSplatArgumentElementNode {
1321+
CfgNodes::ExprNodes::CallCfgNode c;
1322+
int n;
1323+
1324+
SynthSplatArgumentElementNode() { this = TSynthSplatArgumentElementNode(c, n) }
1325+
1326+
CfgNodes::ExprNodes::CallCfgNode getCall() { result = c }
1327+
1328+
int getPosition() { result = n }
1329+
1330+
override CfgScope getCfgScope() { result = c.getExpr().getCfgScope() }
1331+
1332+
override Location getLocationImpl() { result = c.getLocation() }
1333+
1334+
override string toStringImpl() { result = "*[" + n + "]" }
1335+
}
13251336
}
13261337

13271338
import ArgumentNodes
@@ -1592,11 +1603,19 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
15921603
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
15931604
node2.(FlowSummaryNode).getSummaryNode())
15941605
or
1595-
node1 =
1596-
any(SynthSplatParameterElementNode elemNode |
1597-
node2 = elemNode.getSplatParameterNode(_) and
1598-
c = getPositionalContent(elemNode.getStorePosition())
1599-
)
1606+
exists(SynthSplatParameterElementNode elemNode, int splatPos |
1607+
node1 = elemNode and
1608+
node2 = elemNode.getSplatParameterNode(splatPos) and
1609+
c = getPositionalContent(elemNode.getStorePosition() - splatPos)
1610+
)
1611+
or
1612+
// Store from TSynthSplatArgumentElementNode(n)
1613+
// into TSynthSplatArgumentNode[n]
1614+
exists(CfgNodes::ExprNodes::CallCfgNode call, int n |
1615+
node1 = TSynthSplatArgumentElementNode(call, n) and
1616+
node2 = TSynthSplatArgumentNode(call) and
1617+
c = getPositionalContent(n)
1618+
)
16001619
or
16011620
storeStepCommon(node1, c, node2)
16021621
or
@@ -1611,6 +1630,38 @@ predicate readStepCommon(Node node1, ContentSet c, Node node2) {
16111630
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
16121631
or
16131632
node2 = node1.(SynthSplatParameterNode).getAParameter(c)
1633+
or
1634+
// TODO: convert into the above form
1635+
synthSplatArgumentElementReadStep(node1, c, node2)
1636+
or
1637+
// read from SynthSplatArgParameterNode[n] to nth positional parameter
1638+
exists(SynthSplatArgParameterNode argParamNode, NormalParameterNode posNode, int n |
1639+
argParamNode = node1 and
1640+
posNode = node2 and
1641+
posNode
1642+
.isParameterOf(argParamNode.getEnclosingCallable(),
1643+
any(ParameterPosition p | p.isPositional(n))) and
1644+
c = getPositionalContent(n)
1645+
)
1646+
}
1647+
1648+
// read from splat arg to synth splat arg element
1649+
predicate synthSplatArgumentElementReadStep(
1650+
Node node1, ContentSet c, SynthSplatArgumentElementNode node2
1651+
) {
1652+
exists(int n, int splatPos, CfgNodes::ExprNodes::CallCfgNode call |
1653+
// Don't propagate taint on the `self` element of the splat
1654+
// since that won't (probably) won't reach the parameters of the callable.
1655+
// This saves a node per call.
1656+
n >= 0 and
1657+
node1.asExpr().(Argument).isArgumentOf(call, any(ArgumentPosition p | p.isSplat(splatPos))) and
1658+
node2.getCall() = call and
1659+
node2.getPosition() = n + splatPos and
1660+
(
1661+
c = getPositionalContent(n) or
1662+
c.isSingleton(TUnknownElementContent())
1663+
)
1664+
)
16141665
}
16151666

16161667
/**

ruby/ql/test/library-tests/dataflow/params/params_flow.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def kwargs(p1:, **kwargs)
4848

4949
def posargs(p1, *posargs)
5050
sink p1 # $ hasValueFlow=20 $ hasValueFlow=23 $ hasValueFlow=24
51-
sink (posargs[0]) # $ hasValueFlow=22 $ hasValueFlow=21 $ MISSING: hasValueFlow=25
51+
sink (posargs[0]) # $ hasValueFlow=22 $ hasValueFlow=21 $ hasValueFlow=25
5252
sink (posargs[1])
5353
end
5454

0 commit comments

Comments
 (0)