@@ -455,13 +455,9 @@ private module Cached {
455455 exists ( c .asCallable ( ) ) and // exclude library callables
456456 isParameterNode ( _, c , any ( ParameterPosition p | p .isPositional ( _) ) )
457457 } or
458- TSynthSplatArgParameterNode ( DataFlowCallable c ) {
459- exists ( c .asCallable ( ) ) and // exclude library callables
460- isParameterNode ( _, c , any ( ParameterPosition p | p .isSplat ( _) ) )
461- } or
462458 TSynthSplatParameterElementNode ( DataFlowCallable c , int n ) {
463459 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
465461 n in [ 0 .. 10 ]
466462 } or
467463 TExprPostUpdateNode ( CfgNodes:: ExprCfgNode n ) {
@@ -479,15 +475,19 @@ private module Cached {
479475 or
480476 c .getAnArgument ( ) instanceof CfgNodes:: ExprNodes:: PairCfgNode
481477 } 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+ )
485485 } or
486486 TCaptureNode ( VariableCapture:: Flow:: SynthesizedCaptureNode cn )
487487
488488 class TSourceParameterNode =
489489 TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
490- TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode ;
490+ TSynthHashSplatParameterNode or TSynthSplatParameterNode ;
491491
492492 cached
493493 Location getLocation ( NodeImpl n ) { result = n .getLocationImpl ( ) }
@@ -695,8 +695,6 @@ predicate nodeIsHidden(Node n) {
695695 or
696696 n instanceof SynthSplatArgumentNode
697697 or
698- n instanceof SynthSplatArgParameterNode
699- or
700698 n instanceof SynthSplatParameterElementNode
701699 or
702700 n instanceof LambdaSelfReferenceNode
@@ -1026,19 +1024,23 @@ private module ParameterNodes {
10261024 * For example, in the following code:
10271025 *
10281026 * ```rb
1029- * def foo(x, y); end
1027+ * def foo(x, y, z ); end
10301028 *
1031- * foo(*[ a, b ])
1029+ * foo(a, *[b, c ])
10321030 * ```
10331031 *
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
10351033 * a `SynthSplatParameterNode` for the method `foo`, and matching the splat argument to this
10361034 * 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.
10381040 *
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
10401042 * ```rb
1041- * foo(a, *[b])
1043+ * foo(a, *[b], c )
10421044 * ```
10431045 */
10441046 class SynthSplatParameterNode extends ParameterNodeImpl , TSynthSplatParameterNode {
@@ -1047,16 +1049,16 @@ private module ParameterNodes {
10471049 SynthSplatParameterNode ( ) { this = TSynthSplatParameterNode ( callable ) }
10481050
10491051 /**
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:
10531054 *
10541055 * ```rb
1055- * def foo(x, y, a:, *rest)
1056- * end
1056+ * def foo(x, y, a:, *rest); end
1057+ *
1058+ * foo(arg1, *args)
10571059 * ```
10581060 *
1059- * Then `getAParameter(element 0) = x` and `getAParameter(element 1 ) = y`.
1061+ * then `getAParameter(element 0) = y`.
10601062 */
10611063 ParameterNode getAParameter ( ContentSet c ) {
10621064 exists ( int n |
@@ -1084,31 +1086,6 @@ private module ParameterNodes {
10841086 final override string toStringImpl ( ) { result = "synthetic *args" }
10851087 }
10861088
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-
11121089 /**
11131090 * A node that holds the content of a specific positional argument.
11141091 * See `SynthSplatArgumentNode` for more information.
@@ -1127,12 +1104,7 @@ private module ParameterNodes {
11271104
11281105 int getStorePosition ( ) { result = pos }
11291106
1130- int getReadPosition ( ) {
1131- exists ( int splatPos |
1132- exists ( this .getSplatParameterNode ( splatPos ) ) and
1133- result = pos + splatPos
1134- )
1135- }
1107+ int getReadPosition ( ) { result = pos }
11361108
11371109 final override CfgScope getCfgScope ( ) { result = callable .asCallable ( ) }
11381110
@@ -1246,7 +1218,7 @@ module ArgumentNodes {
12461218 * part of the method signature, such that those cannot end up in the hash-splat
12471219 * parameter.
12481220 */
1249- class SynthHashSplatArgumentNode extends ArgumentNode , TSynthHashSplatArgumentNode {
1221+ class SynthHashSplatArgumentNode extends ArgumentNode , NodeImpl , TSynthHashSplatArgumentNode {
12501222 CfgNodes:: ExprNodes:: CallCfgNode c ;
12511223
12521224 SynthHashSplatArgumentNode ( ) { this = TSynthHashSplatArgumentNode ( c ) }
@@ -1259,12 +1231,6 @@ module ArgumentNodes {
12591231 call = c and
12601232 pos .isHashSplat ( )
12611233 }
1262- }
1263-
1264- private class SynthHashSplatArgumentNodeImpl extends NodeImpl , TSynthHashSplatArgumentNode {
1265- CfgNodes:: ExprNodes:: CallCfgNode c ;
1266-
1267- SynthHashSplatArgumentNodeImpl ( ) { this = TSynthHashSplatArgumentNode ( c ) }
12681234
12691235 override CfgScope getCfgScope ( ) { result = c .getExpr ( ) .getCfgScope ( ) }
12701236
@@ -1287,9 +1253,9 @@ module ArgumentNodes {
12871253 *
12881254 * 1. We want `3` to flow to `z[0]` and `4` to flow to `z[1]`. We model this by first storing all arguments
12891255 * 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
12911257 * (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
12931259 * `SynthSplatParameterElementNode`, which is parameterized by the element index (see `readStep`).
12941260 * 4. Finally, we add store steps from these `SynthSplatParameterElementNode`s to the real splat parameter node
12951261 * (see `storeStep`).
@@ -1317,6 +1283,33 @@ module ArgumentNodes {
13171283
13181284 override string toStringImpl ( ) { result = "*" }
13191285 }
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+ }
13201313}
13211314
13221315import ArgumentNodes
@@ -1556,6 +1549,33 @@ predicate storeStepCommon(Node node1, ContentSet c, Node node2) {
15561549 )
15571550}
15581551
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+
15591579/**
15601580 * Holds if data can flow from `node1` to `node2` via an assignment to
15611581 * content `c`.
@@ -1587,11 +1607,13 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
15871607 FlowSummaryImpl:: Private:: Steps:: summaryStoreStep ( node1 .( FlowSummaryNode ) .getSummaryNode ( ) , c ,
15881608 node2 .( FlowSummaryNode ) .getSummaryNode ( ) )
15891609 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 )
15951617 or
15961618 storeStepCommon ( node1 , c , node2 )
15971619 or
@@ -1608,6 +1630,34 @@ predicate readStepCommon(Node node1, ContentSet c, Node node2) {
16081630 node2 = node1 .( SynthSplatParameterNode ) .getAParameter ( c )
16091631}
16101632
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+
16111661/**
16121662 * Holds if there is a read step of content `c` from `node1` to `node2`.
16131663 */
@@ -1638,14 +1688,16 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
16381688 FlowSummaryImpl:: Private:: Steps:: summaryReadStep ( node1 .( FlowSummaryNode ) .getSummaryNode ( ) , c ,
16391689 node2 .( FlowSummaryNode ) .getSummaryNode ( ) )
16401690 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
16421694 node2 =
16431695 any ( SynthSplatParameterElementNode e |
1644- node1 .( SynthSplatArgParameterNode ) .isParameterOf ( e .getEnclosingCallable ( ) , _) and
1696+ node1 .( SynthSplatParameterNode ) .isParameterOf ( e .getEnclosingCallable ( ) , _) and
16451697 c = getPositionalContent ( e .getReadPosition ( ) )
16461698 )
16471699 or
1648- VariableCapture :: readStep ( node1 , any ( Content :: CapturedVariableContent v | c . isSingleton ( v ) ) , node2 )
1700+ synthSplatArgumentElementReadStep ( node1 , c , node2 )
16491701 or
16501702 readStepCommon ( node1 , c , node2 )
16511703}
0 commit comments