@@ -245,6 +245,7 @@ private class Argument extends CfgNodes::ExprCfgNode {
245245 not this .getExpr ( ) instanceof BlockArgument and
246246 not this .getExpr ( ) .( Pair ) .getKey ( ) .getConstantValue ( ) .isSymbol ( _) and
247247 not this .getExpr ( ) instanceof HashSplatExpr and
248+ not this .getExpr ( ) instanceof SplatExpr and
248249 arg .isPositional ( i )
249250 )
250251 or
@@ -261,8 +262,15 @@ private class Argument extends CfgNodes::ExprCfgNode {
261262 arg .isHashSplat ( )
262263 or
263264 this = call .getArgument ( 0 ) and
265+ not exists ( call .getArgument ( 1 ) ) and
264266 this .getExpr ( ) instanceof SplatExpr and
265267 arg .isSplatAll ( )
268+ or
269+ exists ( int pos | pos > 0 or exists ( call .getArgument ( pos + 1 ) ) |
270+ this = call .getArgument ( pos ) and
271+ this .getExpr ( ) instanceof SplatExpr and
272+ arg .isSplat ( pos )
273+ )
266274 }
267275
268276 /** Holds if this expression is the `i`th argument of `c`. */
@@ -300,6 +308,10 @@ private module Cached {
300308 TSynthHashSplatParameterNode ( DataFlowCallable c ) {
301309 isParameterNode ( _, c , any ( ParameterPosition p | p .isKeyword ( _) ) )
302310 } or
311+ TSynthSplatParameterNode ( DataFlowCallable c ) {
312+ exists ( c .asCallable ( ) ) and // exclude library callables
313+ isParameterNode ( _, c , any ( ParameterPosition p | p .isPositional ( _) ) )
314+ } or
303315 TExprPostUpdateNode ( CfgNodes:: ExprCfgNode n ) {
304316 // filter out nodes that clearly don't need post-update nodes
305317 isNonConstantExpr ( n ) and
@@ -318,7 +330,7 @@ private module Cached {
318330
319331 class TSourceParameterNode =
320332 TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
321- TSynthHashSplatParameterNode ;
333+ TSynthHashSplatParameterNode or TSynthSplatParameterNode ;
322334
323335 cached
324336 Location getLocation ( NodeImpl n ) { result = n .getLocationImpl ( ) }
@@ -514,6 +526,8 @@ predicate nodeIsHidden(Node n) {
514526 n instanceof SynthHashSplatParameterNode
515527 or
516528 n instanceof SynthHashSplatArgumentNode
529+ or
530+ n instanceof SynthSplatParameterNode
517531}
518532
519533/** An SSA definition, viewed as a node in a data flow graph. */
@@ -610,7 +624,15 @@ private module ParameterNodes {
610624 pos .isHashSplat ( )
611625 or
612626 parameter = callable .getParameter ( 0 ) .( SplatParameter ) and
627+ not exists ( callable .getParameter ( 1 ) ) and
613628 pos .isSplatAll ( )
629+ or
630+ exists ( int n | n > 0 |
631+ parameter = callable .getParameter ( n ) .( SplatParameter ) and
632+ pos .isSplat ( n ) and
633+ // There are no positional parameters after the splat
634+ not exists ( SimpleParameter p , int m | m > n | p = callable .getParameter ( m ) )
635+ )
614636 )
615637 }
616638
@@ -749,6 +771,70 @@ private module ParameterNodes {
749771 final override string toStringImpl ( ) { result = "**kwargs" }
750772 }
751773
774+ /**
775+ * A synthetic data-flow node to allow flow to positional parameters from a splat argument.
776+ *
777+ * For example, in the following code:
778+ *
779+ * ```rb
780+ * def foo(x, y); end
781+ *
782+ * foo(*[a, b])
783+ * ```
784+ *
785+ * We want `a` to flow to `x` and `b` to flow to `y`. We do this by constructing
786+ * a `SynthSplatParameterNode` for the method `foo`, and matching the splat argument to this
787+ * parameter node via `parameterMatch/2`. We then add read steps from this node to parameters
788+ * `x` and `y`, for content at indices 0 and 1 respectively (see `readStep`).
789+ *
790+ * We don't yet correctly handle cases where the splat argument is not the first argument, e.g. in
791+ * ```rb
792+ * foo(a, *[b])
793+ * ```
794+ */
795+ class SynthSplatParameterNode extends ParameterNodeImpl , TSynthSplatParameterNode {
796+ private DataFlowCallable callable ;
797+
798+ SynthSplatParameterNode ( ) { this = TSynthSplatParameterNode ( callable ) }
799+
800+ /**
801+ * Gets a parameter which will contain the value given by `c`, assuming
802+ * that the method was called with a single splat argument.
803+ * For example, if the synth splat parameter is for the following method
804+ *
805+ * ```rb
806+ * def foo(x, y, a:, *rest)
807+ * end
808+ * ```
809+ *
810+ * Then `getAParameter(element 0) = x` and `getAParameter(element 1) = y`.
811+ */
812+ ParameterNode getAParameter ( ContentSet c ) {
813+ exists ( int n |
814+ isParameterNode ( result , callable , ( any ( ParameterPosition p | p .isPositional ( n ) ) ) ) and
815+ (
816+ c = getPositionalContent ( n )
817+ or
818+ c .isSingleton ( TUnknownElementContent ( ) )
819+ )
820+ )
821+ }
822+
823+ final override Parameter getParameter ( ) { none ( ) }
824+
825+ final override predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
826+ c = callable and pos .isSynthSplat ( )
827+ }
828+
829+ final override CfgScope getCfgScope ( ) { result = callable .asCallable ( ) }
830+
831+ final override DataFlowCallable getEnclosingCallable ( ) { result = callable }
832+
833+ final override Location getLocationImpl ( ) { result = callable .getLocation ( ) }
834+
835+ final override string toStringImpl ( ) { result = "synthetic *args" }
836+ }
837+
752838 /** A parameter for a library callable with a flow summary. */
753839 class SummaryParameterNode extends ParameterNodeImpl , FlowSummaryNode {
754840 private ParameterPosition pos_ ;
@@ -1099,6 +1185,13 @@ private ContentSet getKeywordContent(string name) {
10991185 )
11001186}
11011187
1188+ private ContentSet getPositionalContent ( int n ) {
1189+ exists ( ConstantValue:: ConstantIntegerValue i |
1190+ result .isSingleton ( TKnownElementContent ( i ) ) and
1191+ i .isInt ( n )
1192+ )
1193+ }
1194+
11021195/**
11031196 * Subset of `storeStep` that should be shared with type-tracking.
11041197 */
@@ -1187,6 +1280,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
11871280 or
11881281 node2 = node1 .( SynthHashSplatParameterNode ) .getAKeywordParameter ( c )
11891282 or
1283+ node2 = node1 .( SynthSplatParameterNode ) .getAParameter ( c )
1284+ or
11901285 FlowSummaryImpl:: Private:: Steps:: summaryReadStep ( node1 .( FlowSummaryNode ) .getSummaryNode ( ) , c ,
11911286 node2 .( FlowSummaryNode ) .getSummaryNode ( ) )
11921287}
0 commit comments