Files
codeql/python/ql/src/Variables/UnusedModuleVariable.ql
Taus 88615f427b Python: Add support for forward declarations in unused var query
Fixes the false positive reported in
https://github.com/github/codeql/issues/18910

Adds a new `Annotation` class (subclass of `Expr`) which encompasses all
possible kinds of annotations in Python.

Using this, we look for string literals which are part of an annotation,
and which have the same content as the name of a (potentially) unused
global variable, and in that case we do not produce an alert.

In future, we may want to support inspecting such string literals more
deeply (e.g. to support stuff like "list[unused_var]"), but I think for
now this level of support is sufficient.
2025-03-04 14:41:45 +00:00

76 lines
2.4 KiB
Plaintext

/**
* @name Unused global variable
* @description Global variable is defined but not used
* @kind problem
* @tags efficiency
* useless-code
* external/cwe/cwe-563
* @problem.severity recommendation
* @sub-severity low
* @precision high
* @id py/unused-global-variable
*/
import python
import Definition
/**
* Whether the module contains an __all__ definition,
* but it is more complex than a simple list of strings
*/
predicate complex_all(Module m) {
exists(Assign a, GlobalVariable all |
a.defines(all) and a.getScope() = m and all.getId() = "__all__"
|
not a.getValue() instanceof List
or
exists(Expr e | e = a.getValue().(List).getAnElt() | not e instanceof StringLiteral)
)
or
exists(Call c, GlobalVariable all |
c.getFunc().(Attribute).getObject() = all.getALoad() and
c.getScope() = m and
all.getId() = "__all__"
)
}
predicate used_in_forward_declaration(Name used, Module mod) {
exists(StringLiteral s, Annotation annotation |
s.getS() = used.getId() and
s.getEnclosingModule() = mod and
annotation.getASubExpression*() = s
)
}
predicate unused_global(Name unused, GlobalVariable v) {
not exists(ImportingStmt is | is.contains(unused)) and
forex(DefinitionNode defn | defn.getNode() = unused |
not defn.getValue().getNode() instanceof FunctionExpr and
not defn.getValue().getNode() instanceof ClassExpr and
not exists(Name u |
// A use of the variable
u.uses(v)
|
// That is reachable from this definition, directly
defn.strictlyReaches(u.getAFlowNode())
or
// indirectly
defn.getBasicBlock().reachesExit() and u.getScope() != unused.getScope()
) and
not unused.getEnclosingModule().getAnExport() = v.getId() and
not exists(unused.getParentNode().(ClassDef).getDefinedClass().getADecorator()) and
not exists(unused.getParentNode().(FunctionDef).getDefinedFunction().getADecorator()) and
unused.defines(v) and
not name_acceptable_for_unused_variable(v) and
not complex_all(unused.getEnclosingModule())
) and
not used_in_forward_declaration(unused, unused.getEnclosingModule())
}
from Name unused, GlobalVariable v
where
unused_global(unused, v) and
// If unused is part of a tuple, count it as unused if all elements of that tuple are unused.
forall(Name el | el = unused.getParentNode().(Tuple).getAnElt() | unused_global(el, _))
select unused, "The global variable '" + v.getId() + "' is not used."