CodeQL queries 1.25
Skip to end of metadata
Go to start of metadata

Name: Unused global variable

Description: Global variable is defined but not used

ID: py/unused-global-variable

Kind: problem

Severity: recommendation

Precision: high

Query: UnusedModuleVariable.ql
/**
 * @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 StrConst)
  )
  or
  exists(Call c, GlobalVariable all |
    c.getFunc().(Attribute).getObject() = all.getALoad() and
    c.getScope() = m and
    all.getId() = "__all__"
  )
}

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())
  )
}

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."

A global (module-level) variable is defined (by an assignment) but never used and is not explicitly made public by inclusion in the __all__ list.

It is sometimes necessary to have a variable which is not used. These unused variables should have distinctive names, to make it clear to readers of the code that they are deliberately not used. The most common conventions for indicating this are to name the variable _ or to start the name of the variable with unused or _unused.

The query accepts the following names for variables that are intended to be unused:

  • Any name consisting entirely of underscores.
  • Any name containing unused.
  • The names dummy or empty.
  • Any "special" name of the form __xxx__.

Variables that are defined in a group, for example x, y = func() are handled collectively. If they are all unused, then this is reported. Otherwise they are all treated as used.

Recommendation

If the variable is included for documentation purposes or is otherwise intentionally unused, then change its name to indicate that it is unused, otherwise delete the assignment (taking care not to delete right hand side if it has side effects).

Example

In this example, the random_no variable is never read but its assignment has a side effect. Because of this it is important to only remove the left hand side of the assignment in line 9.

import random

def write_random_to_file():
    no = random.randint(1, 10)
    with open("random.txt", "w") as file:
        file.write(str(no))
    return no

random_no = write_random_to_file()

References