@@ -23,28 +23,26 @@ string getALoopMethodName() {
2323 ]
2424}
2525
26- /** A call to a loop operation. */
26+ /** A loop, represented by a call to a loop operation. */
2727class LoopingCall extends DataFlow:: CallNode {
28- DataFlow :: CallableNode loopBlock ;
28+ Callable loopScope ;
2929
3030 LoopingCall ( ) {
31- this .getMethodName ( ) = getALoopMethodName ( ) and loopBlock = this .getBlock ( ) .asCallable ( )
31+ this .getMethodName ( ) = getALoopMethodName ( ) and
32+ loopScope = this .getBlock ( ) .asCallable ( ) .asCallableAstNode ( )
3233 }
3334
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 }
3537}
3638
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. */
4240predicate usedInLoopControlGuard ( ActiveRecordInstance ar , DataFlow:: Node guard ) {
4341 TaintTracking:: localTaint ( ar , guard ) and
4442 guard = guardForLoopControl ( _, _)
4543}
4644
47- // A guard for controlling the loop
45+ /** Gets a dataflow node that is used to decide whether to break a loop. */
4846DataFlow:: Node guardForLoopControl ( ConditionalExpr cond , Stmt control ) {
4947 result .asExpr ( ) .getAstNode ( ) = cond .getCondition ( ) .getAChild * ( ) and
5048 (
@@ -55,15 +53,14 @@ DataFlow::Node guardForLoopControl(ConditionalExpr cond, Stmt control) {
5553 control = cond .getBranch ( _) .getAChild ( )
5654}
5755
58- from LoopingCall loop , DataFlow :: CallNode call
56+ from LoopingCall loop , ActiveRecordModelFinderCall call
5957where
58+ loop .executesCall ( call ) and
6059 // Disregard loops over constants
6160 not isArrayConstant ( loop .getReceiver ( ) .asExpr ( ) , _) and
6261 // Disregard cases where the looping is influenced by the query result
6362 not usedInLoopControlGuard ( call , _) and
64- happensInLoop ( loop , call ) and
6563 // Only report calls that are likely to be expensive
66- call instanceof ActiveRecordModelFinderCall and
6764 not call .getMethodName ( ) in [ "new" , "create" ]
6865select call ,
6966 "This call to a database query operation happens inside $@, and could be hoisted to a single call outside the loop." ,
0 commit comments