Files
codeql/python/ql/src/Variables/Definition.qll
2022-03-07 18:59:49 +00:00

142 lines
4.8 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
this.getAFlowNode().strictlyReaches(result.getAFlowNode()) and
result.isUse()
}
Name getDefinition() { result = this.getIterationVariable(0).getAStore() }
}