99 */
1010
1111import ruby
12+ private import codeql.ruby.AST
1213import codeql.ruby.Concepts
1314import codeql.ruby.frameworks.ActiveRecord
15+ private import codeql.ruby.TaintTracking
1416
1517string loopMethodName ( ) {
1618 result in [
@@ -33,7 +35,6 @@ predicate happensInLoop(LoopingCall loop, DataFlow::CallNode e) {
3335 loop .getLoopBlock ( ) .asCallableAstNode ( ) = e .asExpr ( ) .getScope ( )
3436}
3537
36- // predicate directLoop(@ruby_while)
3738predicate happensInOuterLoop ( LoopingCall outerLoop , DataFlow:: CallNode e ) {
3839 exists ( LoopingCall innerLoop |
3940 happensInLoop ( outerLoop , innerLoop ) and
@@ -58,10 +59,28 @@ private ActiveRecordInstance getChain(ActiveRecordInstanceMethodCall c) {
5859 result = getChain ( c .getInstance ( ) )
5960}
6061
62+ // The ActiveRecord instance is used to potentially control the loop
63+ predicate usedInLoopControlGuard ( ActiveRecordInstance ar , DataFlow:: Node guard ) {
64+ TaintTracking:: localTaint ( ar , guard ) and
65+ guard = guardForLoopControl ( _, _)
66+ }
67+
68+ // A guard for controlling the loop
69+ DataFlow:: Node guardForLoopControl ( ConditionalExpr cond , Stmt control ) {
70+ result .asExpr ( ) .getAstNode ( ) = cond .getCondition ( ) .getAChild * ( ) and
71+ (
72+ control .( MethodCall ) .getMethodName ( ) = "raise"
73+ or
74+ control instanceof NextStmt
75+ ) and
76+ control = cond .getBranch ( _) .getAChild ( )
77+ }
78+
6179from LoopingCall loop , DataFlow:: CallNode call , string message
6280where
6381 not call .getLocation ( ) .getFile ( ) .getAbsolutePath ( ) .matches ( "%test%" ) and
6482 not call = any ( PluckCall p ) .chaines ( ) and
83+ not usedInLoopControlGuard ( call , _) and
6584 happensInInnermostLoop ( loop , call ) and
6685 (
6786 call instanceof ActiveRecordModelFinderCall and
0 commit comments