@@ -312,6 +312,15 @@ private module Cached {
312
312
exists ( c .asCallable ( ) ) and // exclude library callables
313
313
isParameterNode ( _, c , any ( ParameterPosition p | p .isPositional ( _) ) )
314
314
} or
315
+ TSynthSplatArgParameterNode ( DataFlowCallable c ) {
316
+ exists ( c .asCallable ( ) ) and // exclude library callables
317
+ isParameterNode ( _, c , any ( ParameterPosition p | p .isSplat ( _) ) )
318
+ } or
319
+ TSynthSplatParameterElementNode ( DataFlowCallable c , int n ) {
320
+ exists ( c .asCallable ( ) ) and // exclude library callables
321
+ exists ( ArgumentPosition p | p .isPositional ( n ) ) and
322
+ isParameterNode ( _, c , any ( ParameterPosition p | p .isSplat ( _) ) )
323
+ } or
315
324
TExprPostUpdateNode ( CfgNodes:: ExprCfgNode n ) {
316
325
// filter out nodes that clearly don't need post-update nodes
317
326
isNonConstantExpr ( n ) and
@@ -326,11 +335,17 @@ private module Cached {
326
335
exists ( Argument arg | arg .isArgumentOf ( c , any ( ArgumentPosition pos | pos .isKeyword ( _) ) ) )
327
336
or
328
337
c .getAnArgument ( ) instanceof CfgNodes:: ExprNodes:: PairCfgNode
338
+ } or
339
+ TSynthSplatArgumentNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) {
340
+ not exists ( Argument arg , ArgumentPosition pos | pos .isSplat ( _) or pos .isSplatAll ( ) |
341
+ arg .isArgumentOf ( c , pos )
342
+ )
329
343
}
330
344
331
345
class TSourceParameterNode =
332
346
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
333
- TSynthHashSplatParameterNode or TSynthSplatParameterNode ;
347
+ TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode or
348
+ TSynthSplatParameterElementNode ;
334
349
335
350
cached
336
351
Location getLocation ( NodeImpl n ) { result = n .getLocationImpl ( ) }
@@ -528,6 +543,12 @@ predicate nodeIsHidden(Node n) {
528
543
n instanceof SynthHashSplatArgumentNode
529
544
or
530
545
n instanceof SynthSplatParameterNode
546
+ or
547
+ n instanceof SynthSplatArgumentNode
548
+ or
549
+ n instanceof SynthSplatArgParameterNode
550
+ or
551
+ n instanceof SynthSplatParameterElementNode
531
552
}
532
553
533
554
/** An SSA definition, viewed as a node in a data flow graph. */
@@ -835,6 +856,50 @@ private module ParameterNodes {
835
856
final override string toStringImpl ( ) { result = "synthetic *args" }
836
857
}
837
858
859
+ /**
860
+ * A node that holds all positional arguments passed in a call to `c`.
861
+ * This is a mirror of the `SynthSplatArgumentNode` on the callable side.
862
+ * See `SynthSplatArgumentNode` for more information.
863
+ */
864
+ class SynthSplatArgParameterNode extends ParameterNodeImpl , TSynthSplatArgParameterNode {
865
+ private DataFlowCallable callable ;
866
+
867
+ SynthSplatArgParameterNode ( ) { this = TSynthSplatArgParameterNode ( callable ) }
868
+
869
+ final override Parameter getParameter ( ) { none ( ) }
870
+
871
+ final override predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
872
+ c = callable and pos .isSynthArgSplat ( )
873
+ }
874
+
875
+ final override CfgScope getCfgScope ( ) { result = callable .asCallable ( ) }
876
+
877
+ final override DataFlowCallable getEnclosingCallable ( ) { result = callable }
878
+
879
+ final override Location getLocationImpl ( ) { result = callable .getLocation ( ) }
880
+
881
+ final override string toStringImpl ( ) { result = "synthetic *args" }
882
+ }
883
+
884
+ /**
885
+ * A node that holds the content of a specific positional argument.
886
+ * See `SynthSplatArgumentNode` for more information.
887
+ */
888
+ class SynthSplatParameterElementNode extends TSynthSplatParameterElementNode {
889
+ private DataFlowCallable callable ;
890
+ private int pos ;
891
+
892
+ SynthSplatParameterElementNode ( ) { this = TSynthSplatParameterElementNode ( callable , pos ) }
893
+
894
+ int getPosition ( ) { result = pos }
895
+
896
+ DataFlowCallable getCallable ( ) { result = callable }
897
+
898
+ string toString ( ) { result = "synthetic *args[" + pos + "]" }
899
+
900
+ Location getLocation ( ) { result = callable .getLocation ( ) }
901
+ }
902
+
838
903
/** A parameter for a library callable with a flow summary. */
839
904
class SummaryParameterNode extends ParameterNodeImpl , FlowSummaryNode {
840
905
private ParameterPosition pos_ ;
@@ -980,6 +1045,57 @@ private module ArgumentNodes {
980
1045
981
1046
override string toStringImpl ( ) { result = "**" }
982
1047
}
1048
+
1049
+ /**
1050
+ * A data-flow node that represents all arguments passed to the call.
1051
+ * We use this to model data flow via splat parameters.
1052
+ * Consider this example:
1053
+ *
1054
+ * ```rb
1055
+ * def foo(x, y, *z)
1056
+ * end
1057
+ *
1058
+ * foo(1, 2, 3, 4)
1059
+ * ```
1060
+ *
1061
+ * 1. We want `3` to flow to `z[0]` and `4` to flow to `z[1]`. We model this by first storing all arguments
1062
+ * in a synthetic argument node `SynthSplatArgumentNode` (see `storeStepCommon`).
1063
+ * 2. We match this to an analogous parameter node `SynthSplatArgParameterNode` on the callee side
1064
+ * (see `parameterMatch`).
1065
+ * 3. For each content element stored in the `SynthSplatArgParameterNode`, we add a read step to a separate
1066
+ * `SynthSplatParameterElementNode`, which is parameterised by the element index (see `readStep`).
1067
+ * 4. Finally, we add store steps from these `SynthSplatParameterElementNode`s to the real splat parameter node
1068
+ * (see `storeStep`).
1069
+ * We only add store steps for elements that will not flow to the earlier positional parameters.
1070
+ * In practice that means we ignore elements at index `<= N`, where `N` is the index of the splat parameter.
1071
+ * For the remaining elements we subtract `N` from their index and store them in the splat parameter.
1072
+ */
1073
+ class SynthSplatArgumentNode extends ArgumentNode , TSynthSplatArgumentNode {
1074
+ CfgNodes:: ExprNodes:: CallCfgNode c ;
1075
+
1076
+ SynthSplatArgumentNode ( ) { this = TSynthSplatArgumentNode ( c ) }
1077
+
1078
+ override predicate argumentOf ( DataFlowCall call , ArgumentPosition pos ) {
1079
+ this .sourceArgumentOf ( call .asCall ( ) , pos )
1080
+ }
1081
+
1082
+ override predicate sourceArgumentOf ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition pos ) {
1083
+ call = c and
1084
+ pos .isSynthSplat ( )
1085
+ }
1086
+ }
1087
+
1088
+ private class SynthSplatArgumentNodeImpl extends NodeImpl , TSynthSplatArgumentNode {
1089
+ CfgNodes:: ExprNodes:: CallCfgNode c ;
1090
+
1091
+ SynthSplatArgumentNodeImpl ( ) { this = TSynthSplatArgumentNode ( c ) }
1092
+
1093
+ override CfgScope getCfgScope ( ) { result = c .getExpr ( ) .getCfgScope ( ) }
1094
+
1095
+ override Location getLocationImpl ( ) { result = c .getLocation ( ) }
1096
+
1097
+ override string toStringImpl ( ) { result = "*" }
1098
+ }
983
1099
}
984
1100
985
1101
import ArgumentNodes
@@ -1215,6 +1331,22 @@ predicate storeStepCommon(Node node1, ContentSet c, Node node2) {
1215
1331
c .isSingleton ( TKnownElementContent ( cv ) )
1216
1332
)
1217
1333
)
1334
+ or
1335
+ exists ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition pos |
1336
+ node2 = TSynthSplatArgumentNode ( call ) and
1337
+ node1 .asExpr ( ) .( Argument ) .isArgumentOf ( call , pos )
1338
+ |
1339
+ exists ( int n | pos .isPositional ( n ) and c = getPositionalContent ( n ) )
1340
+ )
1341
+ or
1342
+ // Store from SynthSplatParameterElementNode[n] into SplatParameterNode[m]
1343
+ // where m = n - <position of SplatParameterNode>
1344
+ exists ( SynthSplatParameterElementNode elemNode , NormalParameterNode splatNode , int splatPos |
1345
+ elemNode = node1 and splatNode = node2
1346
+ |
1347
+ splatNode .isParameterOf ( elemNode .getCallable ( ) , any ( ParameterPosition p | p .isSplat ( splatPos ) ) ) and
1348
+ c = getPositionalContent ( elemNode .getPosition ( ) - splatPos )
1349
+ )
1218
1350
}
1219
1351
1220
1352
/**
@@ -1284,6 +1416,15 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
1284
1416
or
1285
1417
FlowSummaryImpl:: Private:: Steps:: summaryReadStep ( node1 .( FlowSummaryNode ) .getSummaryNode ( ) , c ,
1286
1418
node2 .( FlowSummaryNode ) .getSummaryNode ( ) )
1419
+ or
1420
+ // Read from SynthSplatArgParameterNode into SynthSplatParameterElementNode
1421
+ exists ( SynthSplatArgParameterNode fromNode , SynthSplatParameterElementNode toNode , int pos |
1422
+ node1 = fromNode and node2 = toNode
1423
+ |
1424
+ fromNode .isParameterOf ( toNode .getCallable ( ) , _) and
1425
+ c = getPositionalContent ( pos ) and
1426
+ toNode .getPosition ( ) = pos
1427
+ )
1287
1428
}
1288
1429
1289
1430
/**
0 commit comments