@@ -455,13 +455,9 @@ private module Cached {
455
455
exists ( c .asCallable ( ) ) and // exclude library callables
456
456
isParameterNode ( _, c , any ( ParameterPosition p | p .isPositional ( _) ) )
457
457
} or
458
- TSynthSplatArgParameterNode ( DataFlowCallable c ) {
459
- exists ( c .asCallable ( ) ) and // exclude library callables
460
- isParameterNode ( _, c , any ( ParameterPosition p | p .isSplat ( _) ) )
461
- } or
462
458
TSynthSplatParameterElementNode ( DataFlowCallable c , int n ) {
463
459
exists ( c .asCallable ( ) ) and // exclude library callables
464
- isParameterNode ( _, c , any ( ParameterPosition p | p .isSplat ( _ ) ) ) and
460
+ isParameterNode ( _, c , any ( ParameterPosition p | p .isSplat ( any ( int i | i > 0 ) ) ) ) and
465
461
n in [ 0 .. 10 ]
466
462
} or
467
463
TExprPostUpdateNode ( CfgNodes:: ExprCfgNode n ) {
@@ -479,15 +475,19 @@ private module Cached {
479
475
or
480
476
c .getAnArgument ( ) instanceof CfgNodes:: ExprNodes:: PairCfgNode
481
477
} or
482
- TSynthSplatArgumentNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) {
483
- exists ( Argument arg , ArgumentPosition pos | pos .isPositional ( _) | arg .isArgumentOf ( c , pos ) ) and
484
- not exists ( Argument arg , ArgumentPosition pos | pos .isSplat ( _) | arg .isArgumentOf ( c , pos ) )
478
+ TSynthSplatArgumentNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) or
479
+ TSynthSplatArgumentElementNode ( CfgNodes:: ExprNodes:: CallCfgNode c , int n ) {
480
+ // we use -1 to represent data at an unknown index
481
+ n in [ - 1 .. 10 ] and
482
+ exists ( Argument arg , ArgumentPosition pos |
483
+ pos .isSplat ( any ( int p | p > 0 ) ) and arg .isArgumentOf ( c , pos )
484
+ )
485
485
} or
486
486
TCaptureNode ( VariableCapture:: Flow:: SynthesizedCaptureNode cn )
487
487
488
488
class TSourceParameterNode =
489
489
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
490
- TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode ;
490
+ TSynthHashSplatParameterNode or TSynthSplatParameterNode ;
491
491
492
492
cached
493
493
Location getLocation ( NodeImpl n ) { result = n .getLocationImpl ( ) }
@@ -695,8 +695,6 @@ predicate nodeIsHidden(Node n) {
695
695
or
696
696
n instanceof SynthSplatArgumentNode
697
697
or
698
- n instanceof SynthSplatArgParameterNode
699
- or
700
698
n instanceof SynthSplatParameterElementNode
701
699
or
702
700
n instanceof LambdaSelfReferenceNode
@@ -1026,19 +1024,23 @@ private module ParameterNodes {
1026
1024
* For example, in the following code:
1027
1025
*
1028
1026
* ```rb
1029
- * def foo(x, y); end
1027
+ * def foo(x, y, z ); end
1030
1028
*
1031
- * foo(*[ a, b ])
1029
+ * foo(a, *[b, c ])
1032
1030
* ```
1033
1031
*
1034
- * We want `a ` to flow to `x ` and `b ` to flow to `y `. We do this by constructing
1032
+ * We want `b ` to flow to `y ` and `c ` to flow to `z `. We do this by constructing
1035
1033
* a `SynthSplatParameterNode` for the method `foo`, and matching the splat argument to this
1036
1034
* parameter node via `parameterMatch/2`. We then add read steps from this node to parameters
1037
- * `x` and `y`, for content at indices 0 and 1 respectively (see `readStep`).
1035
+ * `y` and `z`, for content at indices 0 and 1 respectively (see `readStep`).
1036
+ *
1037
+ * This node stores the index of the splat argument it is matched to, which allows us to shift
1038
+ * indices correctly when adding read steps. Without this, in the example above we would erroneously
1039
+ * get a read step to `x` at index 0 and `y` at index 1 etc.
1038
1040
*
1039
- * We don't yet correctly handle cases where the splat argument is not the first argument, e.g. in
1041
+ * We don't yet correctly handle cases where a positional argument follows the splat argument, e.g. in
1040
1042
* ```rb
1041
- * foo(a, *[b])
1043
+ * foo(a, *[b], c )
1042
1044
* ```
1043
1045
*/
1044
1046
class SynthSplatParameterNode extends ParameterNodeImpl , TSynthSplatParameterNode {
@@ -1047,16 +1049,16 @@ private module ParameterNodes {
1047
1049
SynthSplatParameterNode ( ) { this = TSynthSplatParameterNode ( callable ) }
1048
1050
1049
1051
/**
1050
- * Gets a parameter which will contain the value given by `c`, assuming
1051
- * that the method was called with a single splat argument.
1052
- * For example, if the synth splat parameter is for the following method
1052
+ * Gets a parameter which will contain the value given by `c`.
1053
+ * For example, if the synth splat parameter is for the following method and method call:
1053
1054
*
1054
1055
* ```rb
1055
- * def foo(x, y, a:, *rest)
1056
- * end
1056
+ * def foo(x, y, a:, *rest); end
1057
+ *
1058
+ * foo(arg1, *args)
1057
1059
* ```
1058
1060
*
1059
- * Then `getAParameter(element 0) = x` and `getAParameter(element 1 ) = y`.
1061
+ * then `getAParameter(element 0) = y`.
1060
1062
*/
1061
1063
ParameterNode getAParameter ( ContentSet c ) {
1062
1064
exists ( int n |
@@ -1084,31 +1086,6 @@ private module ParameterNodes {
1084
1086
final override string toStringImpl ( ) { result = "synthetic *args" }
1085
1087
}
1086
1088
1087
- /**
1088
- * A node that holds all positional arguments passed in a call to `c`.
1089
- * This is a mirror of the `SynthSplatArgumentNode` on the callable side.
1090
- * See `SynthSplatArgumentNode` for more information.
1091
- */
1092
- class SynthSplatArgParameterNode extends ParameterNodeImpl , TSynthSplatArgParameterNode {
1093
- private DataFlowCallable callable ;
1094
-
1095
- SynthSplatArgParameterNode ( ) { this = TSynthSplatArgParameterNode ( callable ) }
1096
-
1097
- final override Parameter getParameter ( ) { none ( ) }
1098
-
1099
- final override predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
1100
- c = callable and pos .isSynthArgSplat ( )
1101
- }
1102
-
1103
- final override CfgScope getCfgScope ( ) { result = callable .asCallable ( ) }
1104
-
1105
- final override DataFlowCallable getEnclosingCallable ( ) { result = callable }
1106
-
1107
- final override Location getLocationImpl ( ) { result = callable .getLocation ( ) }
1108
-
1109
- final override string toStringImpl ( ) { result = "synthetic *args" }
1110
- }
1111
-
1112
1089
/**
1113
1090
* A node that holds the content of a specific positional argument.
1114
1091
* See `SynthSplatArgumentNode` for more information.
@@ -1127,12 +1104,7 @@ private module ParameterNodes {
1127
1104
1128
1105
int getStorePosition ( ) { result = pos }
1129
1106
1130
- int getReadPosition ( ) {
1131
- exists ( int splatPos |
1132
- exists ( this .getSplatParameterNode ( splatPos ) ) and
1133
- result = pos + splatPos
1134
- )
1135
- }
1107
+ int getReadPosition ( ) { result = pos }
1136
1108
1137
1109
final override CfgScope getCfgScope ( ) { result = callable .asCallable ( ) }
1138
1110
@@ -1246,7 +1218,7 @@ module ArgumentNodes {
1246
1218
* part of the method signature, such that those cannot end up in the hash-splat
1247
1219
* parameter.
1248
1220
*/
1249
- class SynthHashSplatArgumentNode extends ArgumentNode , TSynthHashSplatArgumentNode {
1221
+ class SynthHashSplatArgumentNode extends ArgumentNode , NodeImpl , TSynthHashSplatArgumentNode {
1250
1222
CfgNodes:: ExprNodes:: CallCfgNode c ;
1251
1223
1252
1224
SynthHashSplatArgumentNode ( ) { this = TSynthHashSplatArgumentNode ( c ) }
@@ -1259,12 +1231,6 @@ module ArgumentNodes {
1259
1231
call = c and
1260
1232
pos .isHashSplat ( )
1261
1233
}
1262
- }
1263
-
1264
- private class SynthHashSplatArgumentNodeImpl extends NodeImpl , TSynthHashSplatArgumentNode {
1265
- CfgNodes:: ExprNodes:: CallCfgNode c ;
1266
-
1267
- SynthHashSplatArgumentNodeImpl ( ) { this = TSynthHashSplatArgumentNode ( c ) }
1268
1234
1269
1235
override CfgScope getCfgScope ( ) { result = c .getExpr ( ) .getCfgScope ( ) }
1270
1236
@@ -1287,9 +1253,9 @@ module ArgumentNodes {
1287
1253
*
1288
1254
* 1. We want `3` to flow to `z[0]` and `4` to flow to `z[1]`. We model this by first storing all arguments
1289
1255
* in a synthetic argument node `SynthSplatArgumentNode` (see `storeStepCommon`).
1290
- * 2. We match this to an analogous parameter node `SynthSplatArgParameterNode ` on the callee side
1256
+ * 2. We match this to an analogous parameter node `SynthSplatParameterNode ` on the callee side
1291
1257
* (see `parameterMatch`).
1292
- * 3. For each content element stored in the `SynthSplatArgParameterNode `, we add a read step to a separate
1258
+ * 3. For each content element stored in the `SynthSplatParameterNode `, we add a read step to a separate
1293
1259
* `SynthSplatParameterElementNode`, which is parameterized by the element index (see `readStep`).
1294
1260
* 4. Finally, we add store steps from these `SynthSplatParameterElementNode`s to the real splat parameter node
1295
1261
* (see `storeStep`).
@@ -1317,6 +1283,33 @@ module ArgumentNodes {
1317
1283
1318
1284
override string toStringImpl ( ) { result = "*" }
1319
1285
}
1286
+
1287
+ /**
1288
+ * A data-flow node that holds data from values inside splat arguments.
1289
+ * For example, in the following call
1290
+ *
1291
+ * ```rb
1292
+ * foo(1, 2, *[3, 4])
1293
+ * ```
1294
+ *
1295
+ * We add read steps such that `3` flows into `SynthSplatArgumentElementNode(2)` and `4` flows into `SynthSplatArgumentElementNode(3)`.
1296
+ */
1297
+ class SynthSplatArgumentElementNode extends NodeImpl , TSynthSplatArgumentElementNode {
1298
+ CfgNodes:: ExprNodes:: CallCfgNode c ;
1299
+ int n ;
1300
+
1301
+ SynthSplatArgumentElementNode ( ) { this = TSynthSplatArgumentElementNode ( c , n ) }
1302
+
1303
+ CfgNodes:: ExprNodes:: CallCfgNode getCall ( ) { result = c }
1304
+
1305
+ int getPosition ( ) { result = n }
1306
+
1307
+ override CfgScope getCfgScope ( ) { result = c .getExpr ( ) .getCfgScope ( ) }
1308
+
1309
+ override Location getLocationImpl ( ) { result = c .getLocation ( ) }
1310
+
1311
+ override string toStringImpl ( ) { result = "*[" + n + "]" }
1312
+ }
1320
1313
}
1321
1314
1322
1315
import ArgumentNodes
@@ -1556,6 +1549,33 @@ predicate storeStepCommon(Node node1, ContentSet c, Node node2) {
1556
1549
)
1557
1550
}
1558
1551
1552
+ /**
1553
+ * Holds if data can flow from a `SynthSplatArgumentElementNode` into a `SynthSplatArgumentNode` via a store step.
1554
+ * For example in
1555
+ *
1556
+ * ```rb
1557
+ * foo(1, 2, *[3, 4])
1558
+ * ```
1559
+ *
1560
+ * We have flow from `3` into `SynthSplatArgumentElementNode(2)`. This step stores the value from this node into element `2` of the `SynthSplatArgumentNode`.
1561
+ *
1562
+ * This allows us to match values inside splat arguments to the correct parameter in the callable.
1563
+ */
1564
+ predicate synthSplatArgumentElementStoreStep (
1565
+ SynthSplatArgumentElementNode node1 , ContentSet c , SynthSplatArgumentNode node2
1566
+ ) {
1567
+ exists ( CfgNodes:: ExprNodes:: CallCfgNode call , int n |
1568
+ node2 = TSynthSplatArgumentNode ( call ) and
1569
+ node1 = TSynthSplatArgumentElementNode ( call , n ) and
1570
+ (
1571
+ c = getPositionalContent ( n )
1572
+ or
1573
+ n = - 1 and
1574
+ c .isSingleton ( TUnknownElementContent ( ) )
1575
+ )
1576
+ )
1577
+ }
1578
+
1559
1579
/**
1560
1580
* Holds if data can flow from `node1` to `node2` via an assignment to
1561
1581
* content `c`.
@@ -1587,11 +1607,13 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
1587
1607
FlowSummaryImpl:: Private:: Steps:: summaryStoreStep ( node1 .( FlowSummaryNode ) .getSummaryNode ( ) , c ,
1588
1608
node2 .( FlowSummaryNode ) .getSummaryNode ( ) )
1589
1609
or
1590
- node1 =
1591
- any ( SynthSplatParameterElementNode elemNode |
1592
- node2 = elemNode .getSplatParameterNode ( _) and
1593
- c = getPositionalContent ( elemNode .getStorePosition ( ) )
1594
- )
1610
+ exists ( SynthSplatParameterElementNode elemNode , int splatPos |
1611
+ node1 = elemNode and
1612
+ node2 = elemNode .getSplatParameterNode ( splatPos ) and
1613
+ c = getPositionalContent ( elemNode .getStorePosition ( ) - splatPos )
1614
+ )
1615
+ or
1616
+ synthSplatArgumentElementStoreStep ( node1 , c , node2 )
1595
1617
or
1596
1618
storeStepCommon ( node1 , c , node2 )
1597
1619
or
@@ -1608,6 +1630,34 @@ predicate readStepCommon(Node node1, ContentSet c, Node node2) {
1608
1630
node2 = node1 .( SynthSplatParameterNode ) .getAParameter ( c )
1609
1631
}
1610
1632
1633
+ /**
1634
+ * Holds if data can flow from a splat argument to a `SynthSplatArgumentElementNode` via a read step.
1635
+ * For example in
1636
+ * ```rb
1637
+ * foo(x, y, *[1, 2])
1638
+ * ```
1639
+ *
1640
+ * we read `1` into `SynthSplatArgumentElementNode(2)` and `2` into `SynthSplatArgumentElementNode(3)`.
1641
+ */
1642
+ predicate synthSplatArgumentElementReadStep (
1643
+ Node node1 , ContentSet c , SynthSplatArgumentElementNode node2
1644
+ ) {
1645
+ exists ( int splatPos , CfgNodes:: ExprNodes:: CallCfgNode call |
1646
+ node1 .asExpr ( ) .( Argument ) .isArgumentOf ( call , any ( ArgumentPosition p | p .isSplat ( splatPos ) ) ) and
1647
+ splatPos > 0 and
1648
+ node2 .getCall ( ) = call and
1649
+ (
1650
+ exists ( int n |
1651
+ node2 .getPosition ( ) = n + splatPos and
1652
+ c = getPositionalContent ( n )
1653
+ )
1654
+ or
1655
+ node2 .getPosition ( ) = - 1 and
1656
+ c .isSingleton ( TUnknownElementContent ( ) )
1657
+ )
1658
+ )
1659
+ }
1660
+
1611
1661
/**
1612
1662
* Holds if there is a read step of content `c` from `node1` to `node2`.
1613
1663
*/
@@ -1638,14 +1688,16 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
1638
1688
FlowSummaryImpl:: Private:: Steps:: summaryReadStep ( node1 .( FlowSummaryNode ) .getSummaryNode ( ) , c ,
1639
1689
node2 .( FlowSummaryNode ) .getSummaryNode ( ) )
1640
1690
or
1641
- // Read from SynthSplatArgParameterNode into SynthSplatParameterElementNode
1691
+ VariableCapture:: readStep ( node1 , any ( Content:: CapturedVariableContent v | c .isSingleton ( v ) ) , node2 )
1692
+ or
1693
+ // Read from SynthSplatParameterNode into SynthSplatParameterElementNode
1642
1694
node2 =
1643
1695
any ( SynthSplatParameterElementNode e |
1644
- node1 .( SynthSplatArgParameterNode ) .isParameterOf ( e .getEnclosingCallable ( ) , _) and
1696
+ node1 .( SynthSplatParameterNode ) .isParameterOf ( e .getEnclosingCallable ( ) , _) and
1645
1697
c = getPositionalContent ( e .getReadPosition ( ) )
1646
1698
)
1647
1699
or
1648
- VariableCapture :: readStep ( node1 , any ( Content :: CapturedVariableContent v | c . isSingleton ( v ) ) , node2 )
1700
+ synthSplatArgumentElementReadStep ( node1 , c , node2 )
1649
1701
or
1650
1702
readStepCommon ( node1 , c , node2 )
1651
1703
}
0 commit comments