mirror of
https://github.com/github/codeql.git
synced 2026-07-22 03:32:03 +02:00
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.
76 lines
2.4 KiB
Plaintext
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."
|