@@ -179,6 +179,7 @@ private class Argument extends CfgNodes::ExprCfgNode {
179179 this = call .getArgument ( i ) and
180180 not this .getExpr ( ) instanceof BlockArgument and
181181 not this .getExpr ( ) .( Pair ) .getKey ( ) .getConstantValue ( ) .isSymbol ( _) and
182+ not this .getExpr ( ) instanceof HashSplatExpr and
182183 arg .isPositional ( i )
183184 )
184185 or
@@ -189,6 +190,10 @@ private class Argument extends CfgNodes::ExprCfgNode {
189190 )
190191 or
191192 this = call .getReceiver ( ) and arg .isSelf ( )
193+ or
194+ this = call .getAnArgument ( ) and
195+ this .getExpr ( ) instanceof HashSplatExpr and
196+ arg .isHashSplat ( )
192197 }
193198
194199 /** Holds if this expression is the `i`th argument of `c`. */
@@ -216,7 +221,8 @@ private module Cached {
216221 TNormalParameterNode ( Parameter p ) {
217222 p instanceof SimpleParameter or
218223 p instanceof OptionalParameter or
219- p instanceof KeywordParameter
224+ p instanceof KeywordParameter or
225+ p instanceof HashSplatParameter
220226 } or
221227 TSelfParameterNode ( MethodBase m ) or
222228 TBlockParameterNode ( MethodBase m ) or
@@ -232,6 +238,9 @@ private module Cached {
232238 } or
233239 TSummaryParameterNode ( FlowSummaryImpl:: Public:: SummarizedCallable c , ParameterPosition pos ) {
234240 FlowSummaryImpl:: Private:: summaryParameterNodeRange ( c , pos )
241+ } or
242+ THashSplatArgumentsNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) {
243+ exists ( Argument arg | arg .isArgumentOf ( c , any ( ArgumentPosition pos | pos .isKeyword ( _) ) ) )
235244 }
236245
237246 class TParameterNode =
@@ -389,6 +398,8 @@ predicate nodeIsHidden(Node n) {
389398 n instanceof SummaryParameterNode
390399 or
391400 n instanceof SynthReturnNode
401+ or
402+ n instanceof HashSplatArgumentsNode
392403}
393404
394405/** An SSA definition, viewed as a node in a data flow graph. */
@@ -473,6 +484,9 @@ private module ParameterNodes {
473484 c .getAParameter ( ) = kp and
474485 pos .isKeyword ( kp .getName ( ) )
475486 )
487+ or
488+ parameter = c .getAParameter ( ) .( HashSplatParameter ) and
489+ pos .isHashSplat ( )
476490 }
477491
478492 override CfgScope getCfgScope ( ) { result = parameter .getCallable ( ) }
@@ -651,6 +665,40 @@ private module ArgumentNodes {
651665 FlowSummaryImpl:: Private:: summaryArgumentNode ( call , this , pos )
652666 }
653667 }
668+
669+ /**
670+ * A data-flow node that represents all keyword arguments wrapped in a hash.
671+ *
672+ * The callee is responsible for filtering out the keyword arguments that are
673+ * part of the method signature, such that those cannot end up in the hash-splat
674+ * parameter.
675+ */
676+ class HashSplatArgumentsNode extends ArgumentNode , THashSplatArgumentsNode {
677+ CfgNodes:: ExprNodes:: CallCfgNode c ;
678+
679+ HashSplatArgumentsNode ( ) { this = THashSplatArgumentsNode ( c ) }
680+
681+ override predicate argumentOf ( DataFlowCall call , ArgumentPosition pos ) {
682+ this .sourceArgumentOf ( call .asCall ( ) , pos )
683+ }
684+
685+ override predicate sourceArgumentOf ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition pos ) {
686+ call = c and
687+ pos .isHashSplat ( )
688+ }
689+ }
690+
691+ private class HashSplatArgumentsNodeImpl extends NodeImpl , THashSplatArgumentsNode {
692+ CfgNodes:: ExprNodes:: CallCfgNode c ;
693+
694+ HashSplatArgumentsNodeImpl ( ) { this = THashSplatArgumentsNode ( c ) }
695+
696+ override CfgScope getCfgScope ( ) { result = c .getExpr ( ) .getCfgScope ( ) }
697+
698+ override Location getLocationImpl ( ) { result = c .getLocation ( ) }
699+
700+ override string toStringImpl ( ) { result = "**" }
701+ }
654702}
655703
656704import ArgumentNodes
@@ -807,6 +855,13 @@ predicate jumpStep(Node pred, Node succ) {
807855 succ .asExpr ( ) .getExpr ( ) .( ConstantReadAccess ) .getValue ( ) = pred .asExpr ( ) .getExpr ( )
808856}
809857
858+ private ContentSet getKeywordContent ( string name ) {
859+ exists ( ConstantValue:: ConstantSymbolValue key |
860+ result .isSingleton ( TKnownElementContent ( key ) ) and
861+ key .isSymbol ( name )
862+ )
863+ }
864+
810865/**
811866 * Holds if data can flow from `node1` to `node2` via an assignment to
812867 * content `c`.
@@ -845,6 +900,14 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
845900 c .isSingleton ( TUnknownPairValueContent ( ) )
846901 )
847902 )
903+ or
904+ // Wrap all keyword arguments in a synthesized hash-splat argument node
905+ exists ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition keywordPos , string name |
906+ node2 = THashSplatArgumentsNode ( call ) and
907+ node1 .asExpr ( ) .( Argument ) .isArgumentOf ( call , keywordPos ) and
908+ keywordPos .isKeyword ( name ) and
909+ c = getKeywordContent ( name )
910+ )
848911}
849912
850913/**
@@ -870,6 +933,19 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
870933 */
871934predicate clearsContent ( Node n , ContentSet c ) {
872935 FlowSummaryImpl:: Private:: Steps:: summaryClearsContent ( n , c )
936+ or
937+ // Filter out keyword arguments that are part of the method signature from
938+ // the hash-splat parameter
939+ exists (
940+ DataFlowCallable callable , ParameterPosition hashSplatPos , ParameterNodeImpl keywordParam ,
941+ ParameterPosition keywordPos , string name
942+ |
943+ n .( ParameterNodes:: NormalParameterNode ) .isParameterOf ( callable , hashSplatPos ) and
944+ hashSplatPos .isHashSplat ( ) and
945+ keywordParam .isParameterOf ( callable , keywordPos ) and
946+ keywordPos .isKeyword ( name ) and
947+ c = getKeywordContent ( name )
948+ )
873949}
874950
875951/**
0 commit comments