mirror of
https://github.com/github/codeql.git
synced 2026-06-23 13:47:03 +02:00
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>
120 lines
3.4 KiB
Plaintext
120 lines
3.4 KiB
Plaintext
import python
|
|
import Loop
|
|
import semmle.python.dataflow.TaintTracking
|
|
private import LegacyPointsTo
|
|
|
|
/** A marker for "uninitialized". */
|
|
class Uninitialized extends TaintKind {
|
|
Uninitialized() { this = "undefined" }
|
|
}
|
|
|
|
private predicate loop_entry_variables(EssaVariable pred, EssaVariable succ) {
|
|
exists(PhiFunction phi, BasicBlock pb |
|
|
loop_entry_edge(pb, phi.getBasicBlock()) and
|
|
succ = phi.getVariable() and
|
|
pred = phi.getInput(pb)
|
|
)
|
|
}
|
|
|
|
private predicate loop_entry_edge(BasicBlock pred, BasicBlock loop) {
|
|
pred = loop.getAPredecessor() and
|
|
pred = loop.getImmediateDominator() and
|
|
exists(Stmt s, ControlFlowNode sCfg |
|
|
loop_probably_executes_at_least_once(s) and
|
|
sCfg.getNode() = s and
|
|
sCfg.getBasicBlock() = loop
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Since any use of a local will raise if it is uninitialized, then
|
|
* any use dominated by another use of the same variable must be defined, or is unreachable.
|
|
*/
|
|
private predicate first_use(NameNode u, EssaVariable v) {
|
|
v.getASourceUse() = u and
|
|
not exists(NameNode other |
|
|
v.getASourceUse() = other and
|
|
other.strictlyDominates(u)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `call` is a call of the form obj.method_name(...) and
|
|
* there is a function called `method_name` that can exit the program.
|
|
*/
|
|
private predicate maybe_call_to_exiting_function(CallNode call) {
|
|
exists(FunctionValue exits, string name | exits.neverReturns() and exits.getName() = name |
|
|
call.getFunction().(NameNode).getId() = name or
|
|
call.getFunction().(AttrNode).getName() = name
|
|
)
|
|
}
|
|
|
|
predicate exitFunctionGuardedEdge(EssaVariable pred, EssaVariable succ) {
|
|
exists(CallNode exit_call |
|
|
succ.(PhiFunction).getInput(exit_call.getBasicBlock()) = pred and
|
|
maybe_call_to_exiting_function(exit_call)
|
|
)
|
|
}
|
|
|
|
class UninitializedConfig extends TaintTracking::Configuration {
|
|
UninitializedConfig() { this = "Uninitialized local config" }
|
|
|
|
override predicate isSource(DataFlow::Node source, TaintKind kind) {
|
|
kind instanceof Uninitialized and
|
|
exists(EssaVariable var |
|
|
source.asVariable() = var and
|
|
var.getSourceVariable() instanceof FastLocalVariable and
|
|
not var.getSourceVariable().(Variable).escapes()
|
|
|
|
|
var instanceof ScopeEntryDefinition
|
|
or
|
|
var instanceof DeletionDefinition
|
|
)
|
|
}
|
|
|
|
override predicate isBarrier(DataFlow::Node node, TaintKind kind) {
|
|
kind instanceof Uninitialized and
|
|
(
|
|
this.definition(node.asVariable())
|
|
or
|
|
this.use(node.asVariable())
|
|
or
|
|
this.sanitizingNode(node.asCfgNode())
|
|
)
|
|
}
|
|
|
|
private predicate definition(EssaDefinition def) {
|
|
def instanceof AssignmentDefinition
|
|
or
|
|
def instanceof ExceptionCapture
|
|
or
|
|
def instanceof ParameterDefinition
|
|
}
|
|
|
|
private predicate use(EssaDefinition def) {
|
|
exists(def.(EssaNodeRefinement).getInput().getASourceUse())
|
|
or
|
|
exists(def.(PhiFunction).getAnInput().getASourceUse())
|
|
or
|
|
exists(def.(EssaEdgeRefinement).getInput().getASourceUse())
|
|
}
|
|
|
|
private predicate sanitizingNode(ControlFlowNode node) {
|
|
exists(EssaVariable v |
|
|
v.getASourceUse() = node and
|
|
not first_use(node, v)
|
|
)
|
|
}
|
|
|
|
override predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node dest) {
|
|
/*
|
|
* If we are guaranteed to iterate over a loop at least once, then we can prune any edges that
|
|
* don't pass through the body.
|
|
*/
|
|
|
|
loop_entry_variables(src.asVariable(), dest.asVariable())
|
|
or
|
|
exitFunctionGuardedEdge(src.asVariable(), dest.asVariable())
|
|
}
|
|
}
|