Files
codeql/python/ql/src/Variables/UnusedModuleVariable.ql
Copilot 717ff62d70 Python: deprecate AstNode.getAFlowNode() and rewrite internal callers
Preparatory refactor for the shared-CFG dataflow migration.

Deprecates the AstNode.getAFlowNode() cached predicate on the public
Python QL API and rewrites all ~140 internal callers across lib/, src/,
test/, and tools/ from `expr.getAFlowNode() = cfgNode` to
`cfgNode.getNode() = expr`, using ControlFlowNode.getNode() which
already exists in Flow.qll.

The predicate itself is preserved (with a deprecation note pointing at
the new pattern) so external users do not experience churn — they can
migrate at their own pace and the AST/CFG hierarchies still get the
intended untangling once the deprecation eventually elapses.

Semantic noop verified by:
- All 361 lib/ + src/ queries compile clean.
- All 122 ControlFlow + PointsTo library-tests pass.
- All 64 dataflow library-tests pass.
- All 113 Variables/Exceptions/Expressions/Statements/Functions/Imports/
  Security/CWE-798/ModificationOfParameterWithDefault query-tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 14:55:19 +02:00

78 lines
2.5 KiB
Plaintext

/**
* @name Unused global variable
* @description Global variable is defined but not used
* @kind problem
* @tags quality
* maintainability
* useless-code
* external/cwe/cwe-563
* @problem.severity recommendation
* @sub-severity low
* @precision high
* @id py/unused-global-variable
*/
import python
private import LegacyPointsTo
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
exists(ControlFlowNode uCfg | uCfg.getNode() = u | defn.strictlyReaches(uCfg))
or
// indirectly
defn.getBasicBlock().reachesExit() and u.getScope() != unused.getScope()
) and
not unused.getEnclosingModule().(ModuleWithPointsTo).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."