@@ -344,6 +344,15 @@ private module Cached {
344
344
exists ( c .asCallable ( ) ) and // exclude library callables
345
345
isParameterNode ( _, c , any ( ParameterPosition p | p .isPositional ( _) ) )
346
346
} 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
347
356
TExprPostUpdateNode ( CfgNodes:: ExprCfgNode n ) {
348
357
// filter out nodes that clearly don't need post-update nodes
349
358
isNonConstantExpr ( n ) and
@@ -358,11 +367,18 @@ private module Cached {
358
367
exists ( Argument arg | arg .isArgumentOf ( c , any ( ArgumentPosition pos | pos .isKeyword ( _) ) ) )
359
368
or
360
369
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
+ )
361
376
}
362
377
363
378
class TSourceParameterNode =
364
379
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
365
- TSynthHashSplatParameterNode or TSynthSplatParameterNode ;
380
+ TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode or
381
+ TSynthSplatParameterElementNode ;
366
382
367
383
cached
368
384
Location getLocation ( NodeImpl n ) { result = n .getLocationImpl ( ) }
@@ -562,6 +578,12 @@ predicate nodeIsHidden(Node n) {
562
578
n instanceof SynthHashSplatArgumentNode
563
579
or
564
580
n instanceof SynthSplatParameterNode
581
+ or
582
+ n instanceof SynthSplatArgumentNode
583
+ or
584
+ n instanceof SynthSplatArgParameterNode
585
+ or
586
+ n instanceof SynthSplatParameterElementNode
565
587
}
566
588
567
589
/** An SSA definition, viewed as a node in a data flow graph. */
@@ -885,6 +907,69 @@ private module ParameterNodes {
885
907
final override string toStringImpl ( ) { result = "synthetic *args" }
886
908
}
887
909
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
+
888
973
/** A parameter for a library callable with a flow summary. */
889
974
class SummaryParameterNode extends ParameterNodeImpl , FlowSummaryNode {
890
975
private ParameterPosition pos_ ;
@@ -1030,6 +1115,51 @@ private module ArgumentNodes {
1030
1115
1031
1116
override string toStringImpl ( ) { result = "**" }
1032
1117
}
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
+ }
1033
1163
}
1034
1164
1035
1165
import ArgumentNodes
@@ -1269,6 +1399,19 @@ predicate storeStepCommon(Node node1, ContentSet c, Node node2) {
1269
1399
c .isSingleton ( TKnownElementContent ( cv ) )
1270
1400
)
1271
1401
)
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
+ )
1272
1415
}
1273
1416
1274
1417
/**
@@ -1334,10 +1477,17 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
1334
1477
or
1335
1478
node2 = node1 .( SynthHashSplatParameterNode ) .getAKeywordParameter ( c )
1336
1479
or
1337
- node2 = node1 .( SynthSplatParameterNode ) .getAParameter ( c )
1338
- or
1339
1480
FlowSummaryImpl:: Private:: Steps:: summaryReadStep ( node1 .( FlowSummaryNode ) .getSummaryNode ( ) , c ,
1340
1481
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
+ )
1341
1491
}
1342
1492
1343
1493
/**
0 commit comments