@@ -14,6 +14,8 @@ import codeql.ruby.ast.internal.Constant
1414import codeql.ruby.Concepts
1515import codeql.ruby.frameworks.ActiveRecord
1616private import codeql.ruby.TaintTracking
17+ private import codeql.ruby.CFG
18+ private import codeql.ruby.controlflow.internal.Guards as Guards
1719
1820/** Gets the name of a built-in method that involves a loop operation. */
1921string getALoopMethodName ( ) {
@@ -36,21 +38,26 @@ class LoopingCall extends DataFlow::CallNode {
3638 predicate executesCall ( DataFlow:: CallNode c ) { c .asExpr ( ) .getScope ( ) = loopScope }
3739}
3840
39- /** Holds if `ar` influences `guard`, which may control the execution of a loop. */
40- predicate usedInLoopControlGuard ( ActiveRecordInstance ar , DataFlow:: Node guard ) {
41- TaintTracking:: localTaint ( ar , guard ) and
42- guard = guardForLoopControl ( _, _)
41+ /** Holds if `ar` influences a guard that may control the execution of a loop. */
42+ predicate usedInLoopControlGuard ( ActiveRecordInstance ar ) {
43+ exists ( DataFlow:: Node insideGuard , CfgNodes:: ExprCfgNode guard |
44+ // For a guard like `cond && ar`, the whole guard will not be tainted
45+ // so we need to look at the taint of the individual parts.
46+ insideGuard .asExpr ( ) .getExpr ( ) = guard .getExpr ( ) .getAChild * ( )
47+ |
48+ TaintTracking:: localTaint ( ar , insideGuard ) and
49+ guardForLoopControl ( guard , _)
50+ )
4351}
4452
45- /** Gets a dataflow node that is used to decide whether to break a loop. */
46- DataFlow :: Node guardForLoopControl ( ConditionalExpr cond , Stmt control ) {
47- result . asExpr ( ) . getAstNode ( ) = cond . getCondition ( ) . getAChild * ( ) and
53+ /** Holds if `guard` controls `break` and `break` would break out of a loop. */
54+ predicate guardForLoopControl ( CfgNodes :: ExprCfgNode guard , CfgNodes :: AstCfgNode break ) {
55+ Guards :: guardControlsBlock ( guard , break . getBasicBlock ( ) , _ ) and
4856 (
49- control . ( MethodCall ) .getMethodName ( ) = "raise"
57+ break . ( CfgNodes :: ExprNodes :: MethodCallCfgNode ) .getMethodName ( ) = "raise"
5058 or
51- control instanceof NextStmt
52- ) and
53- control = cond .getBranch ( _) .getAChild ( )
59+ break instanceof CfgNodes:: ReturningCfgNode
60+ )
5461}
5562
5663from LoopingCall loop , ActiveRecordModelFinderCall call
5966 // Disregard loops over constants
6067 not isArrayConstant ( loop .getReceiver ( ) .asExpr ( ) , _) and
6168 // Disregard cases where the looping is influenced by the query result
62- not usedInLoopControlGuard ( call , _ ) and
69+ not usedInLoopControlGuard ( call ) and
6370 // Only report calls that are likely to be expensive
6471 not call .getMethodName ( ) in [ "new" , "create" ]
6572select call ,
0 commit comments