Files
codeql/java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.ql
2026-02-23 15:09:50 +01:00

94 lines
3.1 KiB
Plaintext

/**
* @name Constant loop condition
* @description A loop condition that remains constant throughout the iteration
* indicates faulty logic and is likely to cause infinite
* looping.
* @kind problem
* @problem.severity warning
* @precision very-high
* @id java/constant-loop-condition
* @tags quality
* reliability
* correctness
* external/cwe/cwe-835
*/
import java
import semmle.code.java.controlflow.Guards
import semmle.code.java.dataflow.SSA
predicate loopWhileTrue(LoopStmt loop) {
loop instanceof ForStmt and not exists(loop.getCondition())
or
loop.getCondition().(BooleanLiteral).getBooleanValue() = true
}
/**
* Holds if `exit` is a `return` or `break` statement that can exit the loop.
*
* Note that `throw` statements are not considered loop exits here, since a
* loop that appears to have a non-exceptional loop exit that cannot be reached
* is worth flagging even if it has a reachable exceptional loop exit.
*/
predicate loopExit(LoopStmt loop, Stmt exit) {
exit.getEnclosingStmt*() = loop.getBody() and
(
exit instanceof ReturnStmt or
exit.(BreakStmt).getTarget() = loop.getEnclosingStmt*()
)
}
/**
* Holds if `cond` is a condition in the loop that guards all `return` and
* `break` statements that can exit the loop.
*/
predicate loopExitGuard(LoopStmt loop, Expr cond) {
exists(ConditionBlock cb, boolean branch |
cond = cb.getCondition() and
cond.getEnclosingStmt().getEnclosingStmt*() = loop.getBody() and
forex(Stmt exit | loopExit(loop, exit) | cb.controls(exit.getBasicBlock(), branch))
)
}
/**
* Holds if `loop.getCondition() = cond` and the loop can possibly execute more
* than once. That is, loops that are always terminated with a `return` or
* `break` are excluded as they are simply disguised `if`-statements.
*/
predicate mainLoopCondition(LoopStmt loop, Expr cond) {
loop.getCondition() = cond and
exists(BasicBlock condBlock | condBlock.getANode().isBefore(cond) |
1 < strictcount(condBlock.getAPredecessor()) or loop instanceof DoStmt
)
}
predicate ssaDefinitionInLoop(LoopStmt loop, SsaDefinition ssa) {
exists(ControlFlowNode node | node = ssa.getControlFlowNode() |
node.getAstNode().(Stmt).getEnclosingStmt*() = loop or
node.getAstNode().(Expr).getEnclosingStmt().getEnclosingStmt*() = loop
)
}
from LoopStmt loop, Expr cond
where
(
mainLoopCondition(loop, cond)
or
loopWhileTrue(loop) and loopExitGuard(loop, cond)
) and
// None of the ssa variables in `cond` are updated inside the loop.
forex(SsaDefinition ssa, VarRead use | ssa.getARead() = use and use.getParent*() = cond |
not ssaDefinitionInLoop(loop, ssa) or
ssa.getControlFlowNode().asExpr().getParent*() = loop.(ForStmt).getAnInit()
) and
// And `cond` does not use method calls, field reads, or array reads.
not exists(MethodCall ma | ma.getParent*() = cond) and
not exists(FieldRead fa |
// Ignore if field is final
not fa.getField().isFinal() and
fa.getParent*() = cond
) and
not exists(ArrayAccess aa | aa.getParent*() = cond)
select cond, "$@ might not terminate, as this loop condition is constant within the loop.", loop,
"Loop"