@@ -344,6 +344,15 @@ private module Cached {
344344 exists ( c .asCallable ( ) ) and // exclude library callables
345345 isParameterNode ( _, c , any ( ParameterPosition p | p .isPositional ( _) ) )
346346 } or
347+ TSynthSplatArgParameterNode ( DataFlowCallable c ) {
348+ exists ( c .asCallable ( ) ) and // exclude library callables
349+ isParameterNode ( _, c , any ( ParameterPosition p | p .isSplat ( _) ) )
350+ } or
351+ TSynthSplatParameterElementNode ( DataFlowCallable c , int n ) {
352+ exists ( c .asCallable ( ) ) and // exclude library callables
353+ isParameterNode ( _, c , any ( ParameterPosition p | p .isSplat ( _) ) ) and
354+ n in [ 0 .. 10 ]
355+ } or
347356 TExprPostUpdateNode ( CfgNodes:: ExprCfgNode n ) {
348357 // filter out nodes that clearly don't need post-update nodes
349358 isNonConstantExpr ( n ) and
@@ -358,11 +367,18 @@ private module Cached {
358367 exists ( Argument arg | arg .isArgumentOf ( c , any ( ArgumentPosition pos | pos .isKeyword ( _) ) ) )
359368 or
360369 c .getAnArgument ( ) instanceof CfgNodes:: ExprNodes:: PairCfgNode
370+ } or
371+ TSynthSplatArgumentNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) {
372+ exists ( Argument arg , ArgumentPosition pos | pos .isPositional ( _) | arg .isArgumentOf ( c , pos ) ) and
373+ not exists ( Argument arg , ArgumentPosition pos | pos .isSplat ( _) or pos .isSplatAll ( ) |
374+ arg .isArgumentOf ( c , pos )
375+ )
361376 }
362377
363378 class TSourceParameterNode =
364379 TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
365- TSynthHashSplatParameterNode or TSynthSplatParameterNode ;
380+ TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode or
381+ TSynthSplatParameterElementNode ;
366382
367383 cached
368384 Location getLocation ( NodeImpl n ) { result = n .getLocationImpl ( ) }
@@ -562,6 +578,12 @@ predicate nodeIsHidden(Node n) {
562578 n instanceof SynthHashSplatArgumentNode
563579 or
564580 n instanceof SynthSplatParameterNode
581+ or
582+ n instanceof SynthSplatArgumentNode
583+ or
584+ n instanceof SynthSplatArgParameterNode
585+ or
586+ n instanceof SynthSplatParameterElementNode
565587}
566588
567589/** An SSA definition, viewed as a node in a data flow graph. */
@@ -885,6 +907,69 @@ private module ParameterNodes {
885907 final override string toStringImpl ( ) { result = "synthetic *args" }
886908 }
887909
910+ /**
911+ * A node that holds all positional arguments passed in a call to `c`.
912+ * This is a mirror of the `SynthSplatArgumentNode` on the callable side.
913+ * See `SynthSplatArgumentNode` for more information.
914+ */
915+ class SynthSplatArgParameterNode extends ParameterNodeImpl , TSynthSplatArgParameterNode {
916+ private DataFlowCallable callable ;
917+
918+ SynthSplatArgParameterNode ( ) { this = TSynthSplatArgParameterNode ( callable ) }
919+
920+ final override Parameter getParameter ( ) { none ( ) }
921+
922+ final override predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
923+ c = callable and pos .isSynthArgSplat ( )
924+ }
925+
926+ final override CfgScope getCfgScope ( ) { result = callable .asCallable ( ) }
927+
928+ final override DataFlowCallable getEnclosingCallable ( ) { result = callable }
929+
930+ final override Location getLocationImpl ( ) { result = callable .getLocation ( ) }
931+
932+ final override string toStringImpl ( ) { result = "synthetic *args" }
933+ }
934+
935+ /**
936+ * A node that holds the content of a specific positional argument.
937+ * See `SynthSplatArgumentNode` for more information.
938+ */
939+ class SynthSplatParameterElementNode extends ParameterNodeImpl , TSynthSplatParameterElementNode {
940+ private DataFlowCallable callable ;
941+ private int pos ;
942+
943+ SynthSplatParameterElementNode ( ) { this = TSynthSplatParameterElementNode ( callable , pos ) }
944+
945+ pragma [ nomagic]
946+ NormalParameterNode getSplatParameterNode ( int splatPos ) {
947+ result
948+ .isParameterOf ( this .getEnclosingCallable ( ) , any ( ParameterPosition p | p .isSplat ( splatPos ) ) )
949+ }
950+
951+ int getStorePosition ( ) { result = pos }
952+
953+ int getReadPosition ( ) {
954+ exists ( int splatPos |
955+ exists ( this .getSplatParameterNode ( splatPos ) ) and
956+ result = pos + splatPos
957+ )
958+ }
959+
960+ final override Parameter getParameter ( ) { none ( ) }
961+
962+ final override predicate isParameterOf ( DataFlowCallable c , ParameterPosition p ) { none ( ) }
963+
964+ final override CfgScope getCfgScope ( ) { result = callable .asCallable ( ) }
965+
966+ final override DataFlowCallable getEnclosingCallable ( ) { result = callable }
967+
968+ final override Location getLocationImpl ( ) { result = callable .getLocation ( ) }
969+
970+ final override string toStringImpl ( ) { result = "synthetic *args[" + pos + "]" }
971+ }
972+
888973 /** A parameter for a library callable with a flow summary. */
889974 class SummaryParameterNode extends ParameterNodeImpl , FlowSummaryNode {
890975 private ParameterPosition pos_ ;
@@ -1030,6 +1115,51 @@ private module ArgumentNodes {
10301115
10311116 override string toStringImpl ( ) { result = "**" }
10321117 }
1118+
1119+ /**
1120+ * A data-flow node that represents all arguments passed to the call.
1121+ * We use this to model data flow via splat parameters.
1122+ * Consider this example:
1123+ *
1124+ * ```rb
1125+ * def foo(x, y, *z)
1126+ * end
1127+ *
1128+ * foo(1, 2, 3, 4)
1129+ * ```
1130+ *
1131+ * 1. We want `3` to flow to `z[0]` and `4` to flow to `z[1]`. We model this by first storing all arguments
1132+ * in a synthetic argument node `SynthSplatArgumentNode` (see `storeStepCommon`).
1133+ * 2. We match this to an analogous parameter node `SynthSplatArgParameterNode` on the callee side
1134+ * (see `parameterMatch`).
1135+ * 3. For each content element stored in the `SynthSplatArgParameterNode`, we add a read step to a separate
1136+ * `SynthSplatParameterElementNode`, which is parameterized by the element index (see `readStep`).
1137+ * 4. Finally, we add store steps from these `SynthSplatParameterElementNode`s to the real splat parameter node
1138+ * (see `storeStep`).
1139+ * We only add store steps for elements that will not flow to the earlier positional parameters.
1140+ * In practice that means we ignore elements at index `<= N`, where `N` is the index of the splat parameter.
1141+ * For the remaining elements we subtract `N` from their index and store them in the splat parameter.
1142+ */
1143+ class SynthSplatArgumentNode extends ArgumentNode , NodeImpl , TSynthSplatArgumentNode {
1144+ CfgNodes:: ExprNodes:: CallCfgNode c ;
1145+
1146+ SynthSplatArgumentNode ( ) { this = TSynthSplatArgumentNode ( c ) }
1147+
1148+ override predicate argumentOf ( DataFlowCall call , ArgumentPosition pos ) {
1149+ this .sourceArgumentOf ( call .asCall ( ) , pos )
1150+ }
1151+
1152+ override predicate sourceArgumentOf ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition pos ) {
1153+ call = c and
1154+ pos .isSynthSplat ( )
1155+ }
1156+
1157+ override CfgScope getCfgScope ( ) { result = c .getExpr ( ) .getCfgScope ( ) }
1158+
1159+ override Location getLocationImpl ( ) { result = c .getLocation ( ) }
1160+
1161+ override string toStringImpl ( ) { result = "*" }
1162+ }
10331163}
10341164
10351165import ArgumentNodes
@@ -1269,6 +1399,19 @@ predicate storeStepCommon(Node node1, ContentSet c, Node node2) {
12691399 c .isSingleton ( TKnownElementContent ( cv ) )
12701400 )
12711401 )
1402+ or
1403+ exists ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition pos |
1404+ node2 = TSynthSplatArgumentNode ( call ) and
1405+ node1 .asExpr ( ) .( Argument ) .isArgumentOf ( call , pos )
1406+ |
1407+ exists ( int n | pos .isPositional ( n ) and c = getPositionalContent ( n ) )
1408+ )
1409+ or
1410+ node1 =
1411+ any ( SynthSplatParameterElementNode elemNode |
1412+ node2 = elemNode .getSplatParameterNode ( _) and
1413+ c = getPositionalContent ( elemNode .getStorePosition ( ) )
1414+ )
12721415}
12731416
12741417/**
@@ -1334,10 +1477,17 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
13341477 or
13351478 node2 = node1 .( SynthHashSplatParameterNode ) .getAKeywordParameter ( c )
13361479 or
1337- node2 = node1 .( SynthSplatParameterNode ) .getAParameter ( c )
1338- or
13391480 FlowSummaryImpl:: Private:: Steps:: summaryReadStep ( node1 .( FlowSummaryNode ) .getSummaryNode ( ) , c ,
13401481 node2 .( FlowSummaryNode ) .getSummaryNode ( ) )
1482+ or
1483+ node2 = node1 .( SynthSplatParameterNode ) .getAParameter ( c )
1484+ or
1485+ // Read from SynthSplatArgParameterNode into SynthSplatParameterElementNode
1486+ node2 =
1487+ any ( SynthSplatParameterElementNode e |
1488+ node1 .( SynthSplatArgParameterNode ) .isParameterOf ( e .getEnclosingCallable ( ) , _) and
1489+ c = getPositionalContent ( e .getReadPosition ( ) )
1490+ )
13411491}
13421492
13431493/**
0 commit comments