@@ -398,25 +398,29 @@ module VariableCapture {
398
398
CapturedSsaDefinitionExt ( ) { this .getSourceVariable ( ) instanceof CapturedVariable }
399
399
}
400
400
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
409
407
def .getSourceVariable ( ) = v and
410
408
(
411
409
def instanceof Ssa:: WriteDefinition
412
410
or
413
411
def instanceof Ssa:: SelfDefinition
414
412
) 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
420
424
def .getSourceVariable ( ) = v and
421
425
(
422
426
def instanceof Ssa:: CapturedCallDefinition
@@ -425,6 +429,20 @@ module VariableCapture {
425
429
)
426
430
)
427
431
}
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
+ }
428
446
}
429
447
430
448
private predicate splatParameterAt ( Callable c , int pos ) {
@@ -443,7 +461,7 @@ private module Cached {
443
461
cached
444
462
newtype TNode =
445
463
TExprNode ( CfgNodes:: ExprCfgNode n ) or
446
- TReturningNode ( CfgNodes:: ReturningCfgNode n ) or
464
+ TReturningNode ( CfgNodes:: ReturningCfgNode n ) { exists ( n . getReturnedValueNode ( ) ) } or
447
465
TSsaDefinitionExtNode ( SsaImpl:: DefinitionExt def ) or
448
466
TCapturedVariableNode ( VariableCapture:: CapturedVariable v ) or
449
467
TNormalParameterNode ( Parameter p ) {
@@ -478,11 +496,11 @@ private module Cached {
478
496
} or
479
497
TFlowSummaryNode ( FlowSummaryImpl:: Private:: SummaryNode sn ) or
480
498
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 , _, _)
484
503
} or
485
- TSynthSplatArgumentNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) or
486
504
TSynthSplatArgumentShiftNode ( CfgNodes:: ExprNodes:: CallCfgNode c , int splatPos , int n ) {
487
505
// we use -1 to represent data at an unknown index
488
506
n in [ - 1 .. 10 ] and
@@ -652,7 +670,7 @@ private module Cached {
652
670
)
653
671
} or
654
672
TSplatContent ( int i , Boolean shifted ) { i in [ 0 .. 10 ] } or
655
- THashSplatContent ( ConstantValue cv ) { not cv . isInt ( _ ) } or
673
+ THashSplatContent ( ConstantValue:: ConstantSymbolValue cv ) or
656
674
TCapturedVariableContent ( VariableCapture:: CapturedVariable v ) or
657
675
// Only used by type-tracking
658
676
TAttributeName ( string name ) { name = any ( SetterMethodCall c ) .getTargetName ( ) }
@@ -1269,6 +1287,51 @@ module ArgumentNodes {
1269
1287
final override Location getLocationImpl ( ) { result = call_ .getLocation ( ) }
1270
1288
}
1271
1289
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
+
1272
1335
/**
1273
1336
* A data-flow node that represents all keyword arguments wrapped in a hash.
1274
1337
*
@@ -1285,44 +1348,7 @@ module ArgumentNodes {
1285
1348
* Holds if a store-step should be added from argument `arg` into this synthetic
1286
1349
* hash-splat argument.
1287
1350
*/
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 ) }
1326
1352
1327
1353
override predicate sourceArgumentOf ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition pos ) {
1328
1354
call = call_ and
@@ -1332,6 +1358,39 @@ module ArgumentNodes {
1332
1358
override string toStringImpl ( ) { result = "synthetic hash-splat argument" }
1333
1359
}
1334
1360
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
+
1335
1394
/**
1336
1395
* A data-flow node that represents all positional arguments wrapped in an array.
1337
1396
*
@@ -1346,31 +1405,7 @@ module ArgumentNodes {
1346
1405
* Holds if a store-step should be added from argument `arg` into this synthetic
1347
1406
* splat argument.
1348
1407
*/
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 ) }
1374
1409
1375
1410
override predicate sourceArgumentOf ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition pos ) {
1376
1411
call = call_ and
0 commit comments