Files
codeql/python/ql/src/Variables/Definition.qll
Copilot db1e5035b4 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-05 07:57:42 +00:00

146 lines
5.0 KiB
Plaintext

import python
/**
* A control-flow node that defines a variable
*/
class Definition extends NameNode, DefinitionNode {
/**
* Gets the variable defined by this control-flow node.
*/
Variable getVariable() { this.defines(result) }
/**
* Gets the SSA variable corresponding to the current definition. Since SSA variables
* are only generated for definitions with at least one use, not all definitions
* will have an SSA variable.
*/
SsaVariable getSsaVariable() { result.getDefinition() = this }
/**
* The index of this definition in its basic block.
*/
private int indexInBB(BasicBlock bb, Variable v) {
v = this.getVariable() and
this = bb.getNode(result)
}
/**
* The rank of this definition among other definitions of the same variable
* in its basic block. The first definition will have rank 1, and subsequent
* definitions will have sequentially increasing ranks.
*/
private int rankInBB(BasicBlock bb, Variable v) {
exists(int defIdx | defIdx = this.indexInBB(bb, v) |
defIdx = rank[result](int idx, Definition def | idx = def.indexInBB(bb, v) | idx)
)
}
/** Is this definition the first in its basic block for its variable? */
predicate isFirst() { this.rankInBB(_, _) = 1 }
/** Is this definition the last in its basic block for its variable? */
predicate isLast() {
exists(BasicBlock b, Variable v |
this.rankInBB(b, v) = max(Definition other | any() | other.rankInBB(b, v))
)
}
/**
* Is this definition unused? A definition is unused if the value it provides
* is not read anywhere.
*/
predicate isUnused() {
// SSA variables only exist for definitions that have at least one use.
not exists(this.getSsaVariable()) and
// If a variable is used in a foreign scope, all bets are off.
not this.getVariable().escapes() and
// Global variables don't have SSA variables unless the scope is global.
this.getVariable().getScope() = this.getScope() and
// A call to locals() or vars() in the variable scope counts as a use
not exists(Function f, Call c, string locals_or_vars |
c.getScope() = f and
this.getScope() = f and
c.getFunc().(Name).getId() = locals_or_vars
|
locals_or_vars = "locals" or locals_or_vars = "vars"
)
}
/**
* Gets an immediate re-definition of this definition's variable.
*/
Definition getARedef() {
result != this and
exists(Variable var | var = this.getVariable() and var = result.getVariable() |
// Definitions in different basic blocks.
this.isLast() and
reaches_without_redef(var, this.getBasicBlock(), result.getBasicBlock()) and
result.isFirst()
)
or
// Definitions in the same basic block.
exists(BasicBlock common, Variable var |
this.rankInBB(common, var) + 1 = result.rankInBB(common, var)
)
}
/**
* We only consider assignments as potential alert targets, not parameters
* and imports and other name-defining constructs.
* We also ignore anything named "_", "empty", "unused" or "dummy"
*/
predicate isRelevant() {
exists(AstNode p | p = this.getNode().getParentNode() |
p instanceof Assign or p instanceof AugAssign or p instanceof Tuple
) and
not name_acceptable_for_unused_variable(this.getVariable()) and
/* Decorated classes and functions are used */
not exists(this.getNode().getParentNode().(FunctionDef).getDefinedFunction().getADecorator()) and
not exists(this.getNode().getParentNode().(ClassDef).getDefinedClass().getADecorator())
}
}
/**
* Check whether basic block `a` reaches basic block `b` without an intervening
* definition of variable `v`. The relation is not transitive by default, so any
* observed transitivity will be caused by loops in the control-flow graph.
*/
private predicate reaches_without_redef(Variable v, BasicBlock a, BasicBlock b) {
exists(Definition def | a.getASuccessor() = b |
def.getBasicBlock() = a and def.getVariable() = v and maybe_redefined(v)
)
or
exists(BasicBlock mid | reaches_without_redef(v, a, mid) |
not exists(NameNode cfn | cfn.defines(v) | cfn.getBasicBlock() = mid) and
mid.getASuccessor() = b
)
}
private predicate maybe_redefined(Variable v) { strictcount(Definition d | d.defines(v)) > 1 }
predicate name_acceptable_for_unused_variable(Variable var) {
exists(string name | var.getId() = name |
name.regexpMatch("_+") or
name = "empty" or
name.matches("%unused%") or
name = "dummy" or
name.regexpMatch("__.*")
)
}
class ListComprehensionDeclaration extends ListComp {
Name getALeakedVariableUse() {
major_version() = 2 and
this.getIterationVariable(_).getId() = result.getId() and
result.getScope() = this.getScope() and
exists(ControlFlowNode thisCfg, ControlFlowNode resultCfg |
thisCfg.getNode() = this and resultCfg.getNode() = result
|
thisCfg.strictlyReaches(resultCfg)
) and
result.isUse()
}
Name getDefinition() { result = this.getIterationVariable(0).getAStore() }
}