mirror of
https://github.com/github/codeql.git
synced 2025-12-24 20:56:33 +01:00
115 lines
3.7 KiB
Plaintext
115 lines
3.7 KiB
Plaintext
/**
|
|
* @name Suspicious unused loop iteration variable
|
|
* @description A loop iteration variable is unused, which suggests an error.
|
|
* @kind problem
|
|
* @tags maintainability
|
|
* correctness
|
|
* @problem.severity error
|
|
* @sub-severity low
|
|
* @precision high
|
|
* @id py/unused-loop-variable
|
|
*/
|
|
|
|
import python
|
|
import Definition
|
|
|
|
predicate is_increment(Stmt s) {
|
|
/* x += n */
|
|
s.(AugAssign).getValue() instanceof IntegerLiteral
|
|
or
|
|
/* x = x + n */
|
|
exists(Name t, BinaryExpr add |
|
|
t = s.(AssignStmt).getTarget(0) and
|
|
add = s.(AssignStmt).getValue() and
|
|
add.getLeft().(Name).getVariable() = t.getVariable() and
|
|
add.getRight() instanceof IntegerLiteral
|
|
)
|
|
}
|
|
|
|
predicate counting_loop(For f) { is_increment(f.getAStmt()) }
|
|
|
|
predicate empty_loop(For f) { not exists(f.getStmt(1)) and f.getStmt(0) instanceof Pass }
|
|
|
|
predicate one_item_only(For f) {
|
|
not exists(Continue c | f.contains(c)) and
|
|
exists(Stmt s | s = f.getBody().getLastItem() |
|
|
s instanceof Return
|
|
or
|
|
s instanceof Break
|
|
)
|
|
}
|
|
|
|
predicate points_to_call_to_range(ControlFlowNode f) {
|
|
/* (x)range is a function in Py2 and a class in Py3, so we must treat it as a plain object */
|
|
exists(Value range |
|
|
range = Value::named("range") or
|
|
range = Value::named("xrange")
|
|
|
|
|
f = range.getACall()
|
|
)
|
|
or
|
|
/* In case points-to fails due to 'from six.moves import range' or similar. */
|
|
exists(string range | f.getNode().(Call).getFunc().(Name).getId() = range |
|
|
range = "range" or range = "xrange"
|
|
)
|
|
or
|
|
/* Handle list(range(...)) and list(list(range(...))) */
|
|
f.(CallNode).pointsTo().getClass() = ClassValue::list() and
|
|
points_to_call_to_range(f.(CallNode).getArg(0))
|
|
}
|
|
|
|
/** Whether n is a use of a variable that is a not effectively a constant. */
|
|
predicate use_of_non_constant(Name n) {
|
|
exists(Variable var |
|
|
n.uses(var) and
|
|
/* use is local */
|
|
not n.getScope() instanceof Module and
|
|
/* variable is not global */
|
|
not var.getScope() instanceof Module
|
|
|
|
|
/* Defined more than once (dynamically) */
|
|
strictcount(Name def | def.defines(var)) > 1
|
|
or
|
|
exists(For f, Name def | f.contains(def) and def.defines(var))
|
|
or
|
|
exists(While w, Name def | w.contains(def) and def.defines(var))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Whether loop body is implicitly repeating something N times.
|
|
* E.g. queue.add(None)
|
|
*/
|
|
predicate implicit_repeat(For f) {
|
|
not exists(f.getStmt(1)) and
|
|
exists(ImmutableLiteral imm | f.getStmt(0).contains(imm)) and
|
|
not exists(Name n | f.getBody().contains(n) and use_of_non_constant(n))
|
|
}
|
|
|
|
/**
|
|
* Get the CFG node for the iterable relating to the for-statement `f` in a comprehension.
|
|
* The for-statement `f` is the artificial for-statement in a comprehension
|
|
* and the result is the iterable in that comprehension.
|
|
* E.g. gets `x` from `{ y for y in x }`.
|
|
*/
|
|
ControlFlowNode get_comp_iterable(For f) {
|
|
exists(Comp c | c.getFunction().getStmt(0) = f | c.getAFlowNode().getAPredecessor() = result)
|
|
}
|
|
|
|
from For f, Variable v, string msg
|
|
where
|
|
f.getTarget() = v.getAnAccess() and
|
|
not f.getAStmt().contains(v.getAnAccess()) and
|
|
not points_to_call_to_range(f.getIter().getAFlowNode()) and
|
|
not points_to_call_to_range(get_comp_iterable(f)) and
|
|
not name_acceptable_for_unused_variable(v) and
|
|
not f.getScope().getName() = "genexpr" and
|
|
not empty_loop(f) and
|
|
not one_item_only(f) and
|
|
not counting_loop(f) and
|
|
not implicit_repeat(f) and
|
|
if exists(Name del | del.deletes(v) and f.getAStmt().contains(del))
|
|
then msg = "' is deleted, but not used, in the loop body."
|
|
else msg = "' is not used in the loop body."
|
|
select f, "For loop variable '" + v.getId() + msg
|