@@ -398,25 +398,29 @@ module VariableCapture {
398398 CapturedSsaDefinitionExt ( ) { this .getSourceVariable ( ) instanceof CapturedVariable }
399399 }
400400
401- /**
402- * Holds if there is control-flow insensitive data-flow from `node1` to `node2`
403- * involving a captured variable. Only used in type tracking.
404- */
405- predicate flowInsensitiveStep ( Node node1 , Node node2 ) {
406- exists ( CapturedSsaDefinitionExt def , CapturedVariable v |
407- // From an assignment or implicit initialization of a captured variable to its flow-insensitive node
408- def = node1 .( SsaDefinitionExtNode ) .getDefinitionExt ( ) and
401+ // From an assignment or implicit initialization of a captured variable to its flow-insensitive node
402+ private predicate flowInsensitiveWriteStep (
403+ SsaDefinitionExtNode node1 , CapturedVariableNode node2 , CapturedVariable v
404+ ) {
405+ exists ( CapturedSsaDefinitionExt def |
406+ def = node1 .getDefinitionExt ( ) and
409407 def .getSourceVariable ( ) = v and
410408 (
411409 def instanceof Ssa:: WriteDefinition
412410 or
413411 def instanceof Ssa:: SelfDefinition
414412 ) and
415- node2 .( CapturedVariableNode ) .getVariable ( ) = v
416- or
417- // From a captured variable node to its flow-sensitive capture nodes
418- node1 .( CapturedVariableNode ) .getVariable ( ) = v and
419- def = node2 .( SsaDefinitionExtNode ) .getDefinitionExt ( ) and
413+ node2 .getVariable ( ) = v
414+ )
415+ }
416+
417+ // From a captured variable node to its flow-sensitive capture nodes
418+ private predicate flowInsensitiveReadStep (
419+ CapturedVariableNode node1 , SsaDefinitionExtNode node2 , CapturedVariable v
420+ ) {
421+ exists ( CapturedSsaDefinitionExt def |
422+ node1 .getVariable ( ) = v and
423+ def = node2 .getDefinitionExt ( ) and
420424 def .getSourceVariable ( ) = v and
421425 (
422426 def instanceof Ssa:: CapturedCallDefinition
@@ -425,6 +429,20 @@ module VariableCapture {
425429 )
426430 )
427431 }
432+
433+ /**
434+ * Holds if there is control-flow insensitive data-flow from `node1` to `node2`
435+ * involving a captured variable. Only used in type tracking.
436+ */
437+ predicate flowInsensitiveStep ( Node node1 , Node node2 ) {
438+ exists ( CapturedVariable v |
439+ flowInsensitiveWriteStep ( node1 , node2 , v ) and
440+ flowInsensitiveReadStep ( _, _, v )
441+ or
442+ flowInsensitiveReadStep ( node1 , node2 , v ) and
443+ flowInsensitiveWriteStep ( _, _, v )
444+ )
445+ }
428446}
429447
430448private predicate splatParameterAt ( Callable c , int pos ) {
@@ -443,7 +461,7 @@ private module Cached {
443461 cached
444462 newtype TNode =
445463 TExprNode ( CfgNodes:: ExprCfgNode n ) or
446- TReturningNode ( CfgNodes:: ReturningCfgNode n ) or
464+ TReturningNode ( CfgNodes:: ReturningCfgNode n ) { exists ( n . getReturnedValueNode ( ) ) } or
447465 TSsaDefinitionExtNode ( SsaImpl:: DefinitionExt def ) or
448466 TCapturedVariableNode ( VariableCapture:: CapturedVariable v ) or
449467 TNormalParameterNode ( Parameter p ) {
@@ -478,11 +496,11 @@ private module Cached {
478496 } or
479497 TFlowSummaryNode ( FlowSummaryImpl:: Private:: SummaryNode sn ) or
480498 TSynthHashSplatArgumentNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) {
481- exists ( Argument arg | arg .isArgumentOf ( c , any ( ArgumentPosition pos | pos .isKeyword ( _) ) ) )
482- or
483- c .getAnArgument ( ) instanceof CfgNodes:: ExprNodes:: PairCfgNode
499+ ArgumentNodes:: synthHashSplatStore ( c , _, _)
500+ } or
501+ TSynthSplatArgumentNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) {
502+ ArgumentNodes:: synthSplatStore ( c , _, _)
484503 } or
485- TSynthSplatArgumentNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) or
486504 TSynthSplatArgumentShiftNode ( CfgNodes:: ExprNodes:: CallCfgNode c , int splatPos , int n ) {
487505 // we use -1 to represent data at an unknown index
488506 n in [ - 1 .. 10 ] and
@@ -652,7 +670,7 @@ private module Cached {
652670 )
653671 } or
654672 TSplatContent ( int i , Boolean shifted ) { i in [ 0 .. 10 ] } or
655- THashSplatContent ( ConstantValue cv ) { not cv . isInt ( _ ) } or
673+ THashSplatContent ( ConstantValue:: ConstantSymbolValue cv ) or
656674 TCapturedVariableContent ( VariableCapture:: CapturedVariable v ) or
657675 // Only used by type-tracking
658676 TAttributeName ( string name ) { name = any ( SetterMethodCall c ) .getTargetName ( ) }
@@ -1269,6 +1287,51 @@ module ArgumentNodes {
12691287 final override Location getLocationImpl ( ) { result = call_ .getLocation ( ) }
12701288 }
12711289
1290+ /**
1291+ * Holds if a store-step should be added from keyword argument `arg`, belonging to
1292+ * `call`, into a synthetic hash splat argument.
1293+ */
1294+ predicate synthHashSplatStore (
1295+ CfgNodes:: ExprNodes:: CallCfgNode call , CfgNodes:: ExprCfgNode arg , ContentSet c
1296+ ) {
1297+ exists ( ConstantValue cv |
1298+ // symbol key
1299+ exists ( ArgumentPosition keywordPos , string name |
1300+ arg .( Argument ) .isArgumentOf ( call , keywordPos ) and
1301+ keywordPos .isKeyword ( name ) and
1302+ cv .isSymbol ( name )
1303+ )
1304+ or
1305+ // non-symbol key
1306+ exists ( CfgNodes:: ExprNodes:: PairCfgNode pair , CfgNodes:: ExprCfgNode key |
1307+ arg = pair .getValue ( ) and
1308+ pair = call .getAnArgument ( ) and
1309+ key = pair .getKey ( ) and
1310+ cv = key .getConstantValue ( ) and
1311+ not cv .isSymbol ( _)
1312+ )
1313+ |
1314+ if call instanceof CfgNodes:: ExprNodes:: HashLiteralCfgNode
1315+ then
1316+ /*
1317+ * Needed for cases like
1318+ *
1319+ * ```rb
1320+ * hash = { a: taint, b: safe }
1321+ *
1322+ * def foo(a:, b:)
1323+ * sink(a)
1324+ * end
1325+ *
1326+ * foo(**hash)
1327+ * ```
1328+ */
1329+
1330+ c .isSingleton ( Content:: getElementContent ( cv ) )
1331+ else c .isSingleton ( THashSplatContent ( cv ) )
1332+ )
1333+ }
1334+
12721335 /**
12731336 * A data-flow node that represents all keyword arguments wrapped in a hash.
12741337 *
@@ -1285,44 +1348,7 @@ module ArgumentNodes {
12851348 * Holds if a store-step should be added from argument `arg` into this synthetic
12861349 * hash-splat argument.
12871350 */
1288- predicate storeFrom ( Node arg , ContentSet c ) {
1289- exists ( ConstantValue cv |
1290- if call_ instanceof CfgNodes:: ExprNodes:: HashLiteralCfgNode
1291- then
1292- /*
1293- * Needed for cases like
1294- *
1295- * ```rb
1296- * hash = { a: taint, b: safe }
1297- *
1298- * def foo(a:, b:)
1299- * sink(a)
1300- * end
1301- *
1302- * foo(**hash)
1303- * ```
1304- */
1305-
1306- c .isSingleton ( TKnownElementContent ( cv ) )
1307- else c .isSingleton ( THashSplatContent ( cv ) )
1308- |
1309- // symbol key
1310- exists ( ArgumentPosition keywordPos , string name |
1311- arg .asExpr ( ) .( Argument ) .isArgumentOf ( call_ , keywordPos ) and
1312- keywordPos .isKeyword ( name ) and
1313- cv .isSymbol ( name )
1314- )
1315- or
1316- // non-symbol key
1317- exists ( CfgNodes:: ExprNodes:: PairCfgNode pair , CfgNodes:: ExprCfgNode key |
1318- arg .asExpr ( ) = pair .getValue ( ) and
1319- pair = call_ .getAnArgument ( ) and
1320- key = pair .getKey ( ) and
1321- cv = key .getConstantValue ( ) and
1322- not cv .isSymbol ( _)
1323- )
1324- )
1325- }
1351+ predicate storeFrom ( Node arg , ContentSet c ) { synthHashSplatStore ( call_ , arg .asExpr ( ) , c ) }
13261352
13271353 override predicate sourceArgumentOf ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition pos ) {
13281354 call = call_ and
@@ -1332,6 +1358,39 @@ module ArgumentNodes {
13321358 override string toStringImpl ( ) { result = "synthetic hash-splat argument" }
13331359 }
13341360
1361+ /**
1362+ * Holds if a store-step should be added from positional argument `arg`, belonging to
1363+ * `call`, into a synthetic splat argument.
1364+ */
1365+ predicate synthSplatStore ( CfgNodes:: ExprNodes:: CallCfgNode call , Argument arg , ContentSet c ) {
1366+ exists ( int n |
1367+ exists ( ArgumentPosition pos |
1368+ arg .isArgumentOf ( call , pos ) and
1369+ pos .isPositional ( n ) and
1370+ not exists ( int i | splatArgumentAt ( call , i ) and i < n )
1371+ )
1372+ |
1373+ if call instanceof CfgNodes:: ExprNodes:: ArrayLiteralCfgNode
1374+ then
1375+ /*
1376+ * Needed for cases like
1377+ *
1378+ * ```rb
1379+ * arr = [taint, safe]
1380+ *
1381+ * def foo(a, b)
1382+ * sink(a)
1383+ * end
1384+ *
1385+ * foo(*arr)
1386+ * ```
1387+ */
1388+
1389+ c = getArrayContent ( n )
1390+ else c = getSplatContent ( n , false )
1391+ )
1392+ }
1393+
13351394 /**
13361395 * A data-flow node that represents all positional arguments wrapped in an array.
13371396 *
@@ -1346,31 +1405,7 @@ module ArgumentNodes {
13461405 * Holds if a store-step should be added from argument `arg` into this synthetic
13471406 * splat argument.
13481407 */
1349- predicate storeFrom ( Node arg , ContentSet c ) {
1350- exists ( ArgumentPosition pos , int n |
1351- arg .asExpr ( ) .( Argument ) .isArgumentOf ( call_ , pos ) and
1352- pos .isPositional ( n ) and
1353- not exists ( int i | splatArgumentAt ( call_ , i ) and i < n ) and
1354- if call_ instanceof CfgNodes:: ExprNodes:: ArrayLiteralCfgNode
1355- then
1356- /*
1357- * Needed for cases like
1358- *
1359- * ```rb
1360- * arr = [taint, safe]
1361- *
1362- * def foo(a, b)
1363- * sink(a)
1364- * end
1365- *
1366- * foo(*arr)
1367- * ```
1368- */
1369-
1370- c = getArrayContent ( n )
1371- else c = getSplatContent ( n , false )
1372- )
1373- }
1408+ predicate storeFrom ( Node arg , ContentSet c ) { synthSplatStore ( call_ , arg .asExpr ( ) , c ) }
13741409
13751410 override predicate sourceArgumentOf ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition pos ) {
13761411 call = call_ and
0 commit comments