Skip to content

Commit 973db51

Browse files
committed
Ruby: Rework splat argument/parameter matching
1 parent 4e02e34 commit 973db51

File tree

8 files changed

+942
-1581
lines changed

8 files changed

+942
-1581
lines changed

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

+34-12
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ private module Cached {
563563
THashSplatArgumentPosition() or
564564
TSynthHashSplatArgumentPosition() or
565565
TSplatArgumentPosition(int pos) { exists(Call c | c.getArgument(pos) instanceof SplatExpr) } or
566-
TSynthSplatArgumentPosition() or
566+
TSynthSplatArgumentPosition(Boolean hasActualSplat) or
567567
TAnyArgumentPosition() or
568568
TAnyKeywordArgumentPosition()
569569

@@ -590,11 +590,11 @@ private module Cached {
590590
THashSplatParameterPosition() or
591591
TSynthHashSplatParameterPosition() or
592592
TSplatParameterPosition(int pos) {
593-
pos = 0
593+
pos = 0 // needed for flow summaries
594594
or
595595
exists(Parameter p | p.getPosition() = pos and p instanceof SplatParameter)
596596
} or
597-
TSynthSplatParameterPosition() or
597+
TSynthSplatParameterPosition(Boolean hasActualSplat) or
598598
TAnyParameterPosition() or
599599
TAnyKeywordParameterPosition()
600600
}
@@ -1384,7 +1384,9 @@ class ParameterPosition extends TParameterPosition {
13841384
predicate isSplat(int n) { this = TSplatParameterPosition(n) }
13851385

13861386
/** Holds if this position represents a synthetic splat parameter. */
1387-
predicate isSynthSplat() { this = TSynthSplatParameterPosition() }
1387+
predicate isSynthSplat(boolean hasActualSplat) {
1388+
this = TSynthSplatParameterPosition(hasActualSplat)
1389+
}
13881390

13891391
/**
13901392
* Holds if this position represents any parameter, except `self` parameters. This
@@ -1419,7 +1421,11 @@ class ParameterPosition extends TParameterPosition {
14191421
or
14201422
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
14211423
or
1422-
this.isSynthSplat() and result = "synthetic *"
1424+
exists(boolean hasActualSplat, string suffix |
1425+
this.isSynthSplat(hasActualSplat) and
1426+
result = "synthetic *" + suffix and
1427+
if hasActualSplat = true then suffix = " (with actual)" else suffix = ""
1428+
)
14231429
}
14241430
}
14251431

@@ -1459,7 +1465,9 @@ class ArgumentPosition extends TArgumentPosition {
14591465
predicate isSplat(int n) { this = TSplatArgumentPosition(n) }
14601466

14611467
/** Holds if this position represents a synthetic splat argument. */
1462-
predicate isSynthSplat() { this = TSynthSplatArgumentPosition() }
1468+
predicate isSynthSplat(boolean hasActualSplat) {
1469+
this = TSynthSplatArgumentPosition(hasActualSplat)
1470+
}
14631471

14641472
/** Gets a textual representation of this position. */
14651473
string toString() {
@@ -1483,7 +1491,11 @@ class ArgumentPosition extends TArgumentPosition {
14831491
or
14841492
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
14851493
or
1486-
this.isSynthSplat() and result = "synthetic *"
1494+
exists(boolean hasActualSplat, string suffix |
1495+
this.isSynthSplat(hasActualSplat) and
1496+
result = "synthetic *" + suffix and
1497+
if hasActualSplat = true then suffix = " (with actual)" else suffix = ""
1498+
)
14871499
}
14881500
}
14891501

@@ -1519,16 +1531,26 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
15191531
(ppos.isHashSplat() or ppos.isSynthHashSplat()) and
15201532
(apos.isHashSplat() or apos.isSynthHashSplat())
15211533
or
1522-
exists(int pos |
1534+
exists(int pos, boolean hasActualSplatParam, boolean hasActualSplatArg |
15231535
(
1524-
ppos.isSplat(pos)
1536+
ppos.isSplat(pos) and
1537+
hasActualSplatParam = true // dummy value
15251538
or
1526-
ppos.isSynthSplat() and pos = 0
1539+
ppos.isSynthSplat(hasActualSplatParam) and
1540+
pos = 0 and
1541+
// prevent synthetic splat parameters from matching synthetic splat arguments
1542+
// when direct positional matching is possible
1543+
(
1544+
hasActualSplatParam = true
1545+
or
1546+
hasActualSplatArg = true
1547+
)
15271548
) and
15281549
(
1529-
apos.isSplat(pos)
1550+
apos.isSplat(pos) and
1551+
hasActualSplatArg = true // dummy value
15301552
or
1531-
apos.isSynthSplat() and pos = 0
1553+
apos.isSynthSplat(hasActualSplatArg) and pos = 0
15321554
)
15331555
)
15341556
or

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

+20-75
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ private module Cached {
661661
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
662662
)
663663
} or
664-
TSplatContent(int i, Boolean shifted) { i in [0 .. 10] } or
664+
deprecated TSplatContent(int i, Boolean shifted) { i in [0 .. 10] } or
665665
THashSplatContent(ConstantValue::ConstantSymbolValue cv) or
666666
TCapturedVariableContent(VariableCapture::CapturedVariable v) or
667667
// Only used by type-tracking
@@ -686,7 +686,6 @@ private module Cached {
686686
TUnknownElementContentApprox() or
687687
TKnownIntegerElementContentApprox() or
688688
TKnownElementContentApprox(string approx) { approx = approxKnownElementIndex(_) } or
689-
TSplatContentApprox(Boolean shifted) or
690689
THashSplatContentApprox(string approx) { approx = approxKnownElementIndex(_) } or
691690
TNonElementContentApprox(Content c) { not c instanceof Content::ElementContent } or
692691
TCapturedVariableContentApprox(VariableCapture::CapturedVariable v)
@@ -701,14 +700,10 @@ private module Cached {
701700
TSynthHashSplatArgumentType(string methodName) {
702701
methodName = any(SynthHashSplatArgumentNode n).getMethodName()
703702
} or
704-
TSynthSplatArgumentType(string methodName) {
705-
methodName = any(SynthSplatArgumentNode n).getMethodName()
706-
} or
707703
TUnknownDataFlowType()
708704
}
709705

710-
class TElementContent =
711-
TKnownElementContent or TUnknownElementContent or TSplatContent or THashSplatContent;
706+
class TElementContent = TKnownElementContent or TUnknownElementContent or THashSplatContent;
712707

713708
import Cached
714709

@@ -1188,18 +1183,6 @@ private module ParameterNodes {
11881183
* by adding read steps out of the synthesized parameter node to the relevant
11891184
* positional parameters.
11901185
*
1191-
* In order to avoid redundancy (and improve performance) in cases like
1192-
*
1193-
* ```rb
1194-
* foo(a, b, c)
1195-
* ```
1196-
*
1197-
* where direct positional matching is possible, we use a special `SplatContent`
1198-
* (instead of reusing `KnownElementContent`) when we construct a synthesized
1199-
* splat argument (`SynthSplatArgumentNode`) at the call site, and then only
1200-
* add read steps out of this node for actual splat arguments (which will use
1201-
* `KnownElementContent` or `TSplatContent(_, true)`).
1202-
*
12031186
* We don't yet correctly handle cases where a positional argument follows the
12041187
* splat argument, e.g. in
12051188
*
@@ -1220,9 +1203,6 @@ private module ParameterNodes {
12201203
isParameterNode(p, callable, any(ParameterPosition pos | pos.isPositional(n))) and
12211204
not exists(int i | splatParameterAt(callable.asCfgScope(), i) and i < n)
12221205
|
1223-
// Important: do not include `TSplatContent(_, false)` here, as normal parameter matching is possible
1224-
c = getSplatContent(n, true)
1225-
or
12261206
c = getArrayContent(n)
12271207
or
12281208
c.isSingleton(TUnknownElementContent())
@@ -1232,7 +1212,13 @@ private module ParameterNodes {
12321212
final override Parameter getParameter() { none() }
12331213

12341214
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
1235-
c = callable and pos.isSynthSplat()
1215+
c = callable and
1216+
exists(boolean hasActualSplat |
1217+
pos.isSynthSplat(hasActualSplat) and
1218+
if exists(TSynthSplatParameterShiftNode(c, _, _))
1219+
then hasActualSplat = true
1220+
else hasActualSplat = false
1221+
)
12361222
}
12371223

12381224
final override CfgScope getCfgScope() { result = callable.asCfgScope() }
@@ -1271,11 +1257,7 @@ private module ParameterNodes {
12711257
*/
12721258
predicate readFrom(SynthSplatParameterNode synthSplat, ContentSet cs) {
12731259
synthSplat.isParameterOf(callable, _) and
1274-
(
1275-
cs = getSplatContent(pos + splatPos, _)
1276-
or
1277-
cs = getArrayContent(pos + splatPos)
1278-
)
1260+
cs = getArrayContent(pos + splatPos)
12791261
}
12801262

12811263
/**
@@ -1506,31 +1488,11 @@ module ArgumentNodes {
15061488
* `call`, into a synthetic splat argument.
15071489
*/
15081490
predicate synthSplatStore(CfgNodes::ExprNodes::CallCfgNode call, Argument arg, ContentSet c) {
1509-
exists(int n |
1510-
exists(ArgumentPosition pos |
1511-
arg.isArgumentOf(call, pos) and
1512-
pos.isPositional(n) and
1513-
not exists(int i | splatArgumentAt(call, i) and i < n)
1514-
)
1515-
|
1516-
if call instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode
1517-
then
1518-
/*
1519-
* Needed for cases like
1520-
*
1521-
* ```rb
1522-
* arr = [taint, safe]
1523-
*
1524-
* def foo(a, b)
1525-
* sink(a)
1526-
* end
1527-
*
1528-
* foo(*arr)
1529-
* ```
1530-
*/
1531-
1532-
c = getArrayContent(n)
1533-
else c = getSplatContent(n, false)
1491+
exists(int n, ArgumentPosition pos |
1492+
arg.isArgumentOf(call, pos) and
1493+
pos.isPositional(n) and
1494+
not exists(int i | splatArgumentAt(call, i) and i < n) and
1495+
c = getArrayContent(n)
15341496
)
15351497
}
15361498

@@ -1552,7 +1514,9 @@ module ArgumentNodes {
15521514

15531515
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
15541516
call = call_ and
1555-
pos.isSynthSplat()
1517+
if any(SynthSplatArgumentShiftNode shift).storeInto(this, _)
1518+
then pos.isSynthSplat(true)
1519+
else pos.isSynthSplat(false)
15561520
}
15571521

15581522
override string toStringImpl() { result = "synthetic splat argument" }
@@ -1583,8 +1547,6 @@ module ArgumentNodes {
15831547
predicate readFrom(Node splatArg, ContentSet cs) {
15841548
splatArg.asExpr().(Argument).isArgumentOf(c, any(ArgumentPosition p | p.isSplat(splatPos))) and
15851549
(
1586-
cs = getSplatContent(n - splatPos, _)
1587-
or
15881550
cs = getArrayContent(n - splatPos)
15891551
or
15901552
n = -1 and
@@ -1599,7 +1561,7 @@ module ArgumentNodes {
15991561
predicate storeInto(SynthSplatArgumentNode synthSplat, ContentSet cs) {
16001562
synthSplat = TSynthSplatArgumentNode(c) and
16011563
(
1602-
cs = getSplatContent(n, true)
1564+
cs = getArrayContent(n)
16031565
or
16041566
n = -1 and
16051567
cs.isSingleton(TUnknownElementContent())
@@ -1813,10 +1775,6 @@ private ContentSet getArrayContent(int n) {
18131775
)
18141776
}
18151777

1816-
private ContentSet getSplatContent(int n, boolean adjusted) {
1817-
result.isSingleton(TSplatContent(n, adjusted))
1818-
}
1819-
18201778
/**
18211779
* Subset of `storeStep` that should be shared with type-tracking.
18221780
*/
@@ -1979,11 +1937,9 @@ DataFlowType getNodeType(Node n) {
19791937
or
19801938
result = TSynthHashSplatArgumentType(n.(SynthHashSplatArgumentNode).getMethodName())
19811939
or
1982-
result = TSynthSplatArgumentType(n.(SynthSplatArgumentNode).getMethodName())
1983-
or
19841940
not n instanceof LambdaSelfReferenceNode and
19851941
not mustHaveLambdaType(n, _) and
1986-
not n instanceof SynthHashSplatOrSplatArgumentNode and
1942+
not n instanceof SynthHashSplatArgumentNode and
19871943
result = TUnknownDataFlowType()
19881944
}
19891945

@@ -2209,12 +2165,6 @@ class ContentApprox extends TContentApprox {
22092165
result = "approximated element " + approx
22102166
)
22112167
or
2212-
exists(boolean shifted, string s |
2213-
this = TSplatContentApprox(shifted) and
2214-
(if shifted = true then s = " (shifted)" else s = "") and
2215-
result = "approximated splat position" + s
2216-
)
2217-
or
22182168
exists(string s |
22192169
this = THashSplatContentApprox(s) and
22202170
result = "approximated hash-splat position " + s
@@ -2259,11 +2209,6 @@ ContentApprox getContentApprox(Content c) {
22592209
result =
22602210
TKnownElementContentApprox(approxKnownElementIndex(c.(Content::KnownElementContent).getIndex()))
22612211
or
2262-
exists(boolean shifted |
2263-
c = TSplatContent(_, shifted) and
2264-
result = TSplatContentApprox(shifted)
2265-
)
2266-
or
22672212
result = THashSplatContentApprox(approxKnownElementIndex(c.(Content::HashSplatContent).getKey()))
22682213
or
22692214
result = TNonElementContentApprox(c)

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

+2-11
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ module Content {
586586
*
587587
* we have an implicit splat argument containing `[1, 2, 3]`.
588588
*/
589-
class SplatContent extends ElementContent, TSplatContent {
589+
deprecated class SplatContent extends Content, TSplatContent {
590590
private int i;
591591
private boolean shifted;
592592

@@ -797,20 +797,14 @@ class ContentSet extends TContentSet {
797797
private Content getAnElementReadContent() {
798798
exists(Content::KnownElementContent c | this.isKnownOrUnknownElement(c) |
799799
result = c or
800-
result = TSplatContent(c.getIndex().getInt(), _) or
801800
result = THashSplatContent(c.getIndex()) or
802801
result = TUnknownElementContent()
803802
)
804803
or
805804
exists(int lower, boolean includeUnknown |
806805
this = TElementLowerBoundContent(lower, includeUnknown)
807806
|
808-
exists(int i |
809-
result.(Content::KnownElementContent).getIndex().isInt(i) or
810-
result = TSplatContent(i, _)
811-
|
812-
i >= lower
813-
)
807+
exists(int i | result.(Content::KnownElementContent).getIndex().isInt(i) | i >= lower)
814808
or
815809
includeUnknown = true and
816810
result = TUnknownElementContent()
@@ -821,9 +815,6 @@ class ContentSet extends TContentSet {
821815
|
822816
type = result.(Content::KnownElementContent).getIndex().getValueType()
823817
or
824-
type = "int" and
825-
result instanceof Content::SplatContent
826-
or
827818
type = result.(Content::HashSplatContent).getKey().getValueType()
828819
or
829820
includeUnknown = true and

0 commit comments

Comments
 (0)