@@ -14,6 +14,8 @@ import codeql.ruby.ast.internal.Constant
14
14
import codeql.ruby.Concepts
15
15
import codeql.ruby.frameworks.ActiveRecord
16
16
private import codeql.ruby.TaintTracking
17
+ private import codeql.ruby.CFG
18
+ private import codeql.ruby.controlflow.internal.Guards as Guards
17
19
18
20
/** Gets the name of a built-in method that involves a loop operation. */
19
21
string getALoopMethodName ( ) {
@@ -36,21 +38,26 @@ class LoopingCall extends DataFlow::CallNode {
36
38
predicate executesCall ( DataFlow:: CallNode c ) { c .asExpr ( ) .getScope ( ) = loopScope }
37
39
}
38
40
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
+ )
43
51
}
44
52
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
48
56
(
49
- control . ( MethodCall ) .getMethodName ( ) = "raise"
57
+ break . ( CfgNodes :: ExprNodes :: MethodCallCfgNode ) .getMethodName ( ) = "raise"
50
58
or
51
- control instanceof NextStmt
52
- ) and
53
- control = cond .getBranch ( _) .getAChild ( )
59
+ break instanceof CfgNodes:: ReturningCfgNode
60
+ )
54
61
}
55
62
56
63
from LoopingCall loop , ActiveRecordModelFinderCall call
59
66
// Disregard loops over constants
60
67
not isArrayConstant ( loop .getReceiver ( ) .asExpr ( ) , _) and
61
68
// Disregard cases where the looping is influenced by the query result
62
- not usedInLoopControlGuard ( call , _ ) and
69
+ not usedInLoopControlGuard ( call ) and
63
70
// Only report calls that are likely to be expensive
64
71
not call .getMethodName ( ) in [ "new" , "create" ]
65
72
select call ,
0 commit comments