88 * @tags performance
99 */
1010
11- // Consider also Associations.
12- // Associations are lazy-loading by default, so something like
13- // in a loop over article do
14- // article.book
15- // if you have 1000 articles it will do a 1000 calls to book.
16- // If you already did article includes book, there should be no problem.
11+ // Possible Improvements;
12+ // - Consider also Associations.
13+ // Associations are lazy-loading by default, so something like
14+ // in a loop over `article` do
15+ // `article.book`
16+ // if you have 1000 articles it will do a 1000 calls to `book`.
17+ // If you already did `article includes book`, there should be no problem.
18+ // - Consider instances of ActiveRecordInstanceMethodCall, for instance
19+ // calls to `pluck`.
1720import ruby
1821private import codeql.ruby.AST
1922import codeql.ruby.ast.internal.Constant
@@ -54,18 +57,6 @@ predicate happensInInnermostLoop(LoopingCall loop, DataFlow::CallNode e) {
5457 not happensInOuterLoop ( loop , e )
5558}
5659
57- private class PluckCall extends ActiveRecordInstanceMethodCall {
58- PluckCall ( ) { this .getMethodName ( ) in [ "pluck" ] }
59-
60- ActiveRecordInstance chaines ( ) { result = getChain ( this ) }
61- }
62-
63- private ActiveRecordInstance getChain ( ActiveRecordInstanceMethodCall c ) {
64- result = c .getInstance ( )
65- or
66- result = getChain ( c .getInstance ( ) )
67- }
68-
6960// The ActiveRecord instance is used to potentially control the loop
7061predicate usedInLoopControlGuard ( ActiveRecordInstance ar , DataFlow:: Node guard ) {
7162 TaintTracking:: localTaint ( ar , guard ) and
@@ -85,14 +76,15 @@ DataFlow::Node guardForLoopControl(ConditionalExpr cond, Stmt control) {
8576
8677from LoopingCall loop , DataFlow:: CallNode call
8778where
88- // Filter loops over constants
79+ // Disregard loops over constants
8980 not isArrayConstant ( loop .getReceiver ( ) .asExpr ( ) , _) and
90- // Filter tests
81+ // Disregard tests
9182 not call .getLocation ( ) .getFile ( ) .getAbsolutePath ( ) .matches ( "%test%" ) and
92- not call = any ( PluckCall p ) . chaines ( ) and
83+ // Disregard cases where the looping is influenced by the query result
9384 not usedInLoopControlGuard ( call , _) and
85+ // Only report the inner most loop
9486 happensInInnermostLoop ( loop , call ) and
95- ( call instanceof ActiveRecordModelFinderCall or call instanceof PluckCall ) and
96- not call . getMethodName ( ) in [ "new" , "create" ] and
97- call .getLocation ( ) . getFile ( ) . getAbsolutePath ( ) . matches ( [ "%app/models/s% ", "%app/models/e%" ] )
87+ // Only report calls that are likely to be expensive
88+ call instanceof ActiveRecordModelFinderCall and
89+ not call .getMethodName ( ) in [ "new ", "create" ]
9890select call , "This call happens inside $@, and could be hoisted." , loop , "this loop"
0 commit comments