diff --git a/ruby/ql/src/queries/performance/CouldBeAsync.ql b/ruby/ql/src/queries/performance/CouldBeAsync.ql index c86e0f7263ce..b4e4ea686d96 100644 --- a/ruby/ql/src/queries/performance/CouldBeAsync.ql +++ b/ruby/ql/src/queries/performance/CouldBeAsync.ql @@ -9,8 +9,10 @@ */ import ruby +private import codeql.ruby.AST import codeql.ruby.Concepts import codeql.ruby.frameworks.ActiveRecord +private import codeql.ruby.TaintTracking string loopMethodName() { result in [ @@ -33,7 +35,6 @@ predicate happensInLoop(LoopingCall loop, DataFlow::CallNode e) { loop.getLoopBlock().asCallableAstNode() = e.asExpr().getScope() } -// predicate directLoop(@ruby_while) predicate happensInOuterLoop(LoopingCall outerLoop, DataFlow::CallNode e) { exists(LoopingCall innerLoop | happensInLoop(outerLoop, innerLoop) and @@ -58,10 +59,28 @@ private ActiveRecordInstance getChain(ActiveRecordInstanceMethodCall c) { result = getChain(c.getInstance()) } +// The ActiveRecord instance is used to potentially control the loop +predicate usedInLoopControlGuard(ActiveRecordInstance ar, DataFlow::Node guard) { + TaintTracking::localTaint(ar, guard) and + guard = guardForLoopControl(_, _) +} + +// A guard for controlling the loop +DataFlow::Node guardForLoopControl(ConditionalExpr cond, Stmt control) { + result.asExpr().getAstNode() = cond.getCondition().getAChild*() and + ( + control.(MethodCall).getMethodName() = "raise" + or + control instanceof NextStmt + ) and + control = cond.getBranch(_).getAChild() +} + from LoopingCall loop, DataFlow::CallNode call, string message where not call.getLocation().getFile().getAbsolutePath().matches("%test%") and not call = any(PluckCall p).chaines() and + not usedInLoopControlGuard(call, _) and happensInInnermostLoop(loop, call) and ( call instanceof ActiveRecordModelFinderCall and