@@ -23,28 +23,26 @@ string getALoopMethodName() {
23
23
]
24
24
}
25
25
26
- /** A call to a loop operation. */
26
+ /** A loop, represented by a call to a loop operation. */
27
27
class LoopingCall extends DataFlow:: CallNode {
28
- DataFlow :: CallableNode loopBlock ;
28
+ Callable loopScope ;
29
29
30
30
LoopingCall ( ) {
31
- this .getMethodName ( ) = getALoopMethodName ( ) and loopBlock = this .getBlock ( ) .asCallable ( )
31
+ this .getMethodName ( ) = getALoopMethodName ( ) and
32
+ loopScope = this .getBlock ( ) .asCallable ( ) .asCallableAstNode ( )
32
33
}
33
34
34
- DataFlow:: CallableNode getLoopBlock ( ) { result = loopBlock }
35
+ /** Holds if `c` is executed as part of the body of this loop. */
36
+ predicate executesCall ( DataFlow:: CallNode c ) { c .asExpr ( ) .getScope ( ) = loopScope }
35
37
}
36
38
37
- predicate happensInLoop ( LoopingCall loop , DataFlow:: CallNode e ) {
38
- loop .getLoopBlock ( ) .asCallableAstNode ( ) = e .asExpr ( ) .getScope ( )
39
- }
40
-
41
- // The ActiveRecord instance is used to potentially control the loop
39
+ /** Holds if `ar` influences `guard`, which may control the execution of a loop. */
42
40
predicate usedInLoopControlGuard ( ActiveRecordInstance ar , DataFlow:: Node guard ) {
43
41
TaintTracking:: localTaint ( ar , guard ) and
44
42
guard = guardForLoopControl ( _, _)
45
43
}
46
44
47
- // A guard for controlling the loop
45
+ /** Gets a dataflow node that is used to decide whether to break a loop. */
48
46
DataFlow:: Node guardForLoopControl ( ConditionalExpr cond , Stmt control ) {
49
47
result .asExpr ( ) .getAstNode ( ) = cond .getCondition ( ) .getAChild * ( ) and
50
48
(
@@ -55,15 +53,14 @@ DataFlow::Node guardForLoopControl(ConditionalExpr cond, Stmt control) {
55
53
control = cond .getBranch ( _) .getAChild ( )
56
54
}
57
55
58
- from LoopingCall loop , DataFlow :: CallNode call
56
+ from LoopingCall loop , ActiveRecordModelFinderCall call
59
57
where
58
+ loop .executesCall ( call ) and
60
59
// Disregard loops over constants
61
60
not isArrayConstant ( loop .getReceiver ( ) .asExpr ( ) , _) and
62
61
// Disregard cases where the looping is influenced by the query result
63
62
not usedInLoopControlGuard ( call , _) and
64
- happensInLoop ( loop , call ) and
65
63
// Only report calls that are likely to be expensive
66
- call instanceof ActiveRecordModelFinderCall and
67
64
not call .getMethodName ( ) in [ "new" , "create" ]
68
65
select call ,
69
66
"This call to a database query operation happens inside $@, and could be hoisted to a single call outside the loop." ,
0 commit comments