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>
This commit is contained in:
Copilot
2026-06-01 10:53:39 +00:00
committed by yoff
parent 7a3f546587
commit db1e5035b4
68 changed files with 274 additions and 198 deletions

View File

@@ -5,5 +5,7 @@
import python
select count(Comprehension c |
count(c.toString()) != 1 or count(c.getLocation()) != 1 or not exists(c.getAFlowNode())
count(c.toString()) != 1 or
count(c.getLocation()) != 1 or
not exists(ControlFlowNode n | n.getNode() = c)
)

View File

@@ -45,13 +45,15 @@ private class VersionGuardedNode extends DataFlow::Node {
VersionGuardedNode() {
version in [2, 3] and
exists(If parent, CompareNode c | parent.getBody().contains(this.asExpr()) |
exists(If parent, CompareNode c, ControlFlowNode litCfg |
parent.getBody().contains(this.asExpr()) and
litCfg.getNode() = any(IntegerLiteral lit | lit.getValue() = version)
|
c.operands(API::moduleImport("sys")
.getMember("version_info")
.getASubscript()
.asSource()
.asCfgNode(), any(Eq eq),
any(IntegerLiteral lit | lit.getValue() = version).getAFlowNode())
.asCfgNode(), any(Eq eq), litCfg)
)
}

View File

@@ -9,7 +9,7 @@ Expr assignedValue(Name n) {
from Name def, DefinitionNode d
where
d = def.getAFlowNode() and
d.getNode() = def and
exists(assignedValue(def)) and
not d.getValue().getNode() = assignedValue(def)
select def.toString(), assignedValue(def)

View File

@@ -8,4 +8,4 @@ where
not a instanceof ExprStmt and
a.getScope() = s and
s instanceof Function
select a.getLocation().getStartLine(), s.getName(), a, count(a.getAFlowNode())
select a.getLocation().getStartLine(), s.getName(), a, count(ControlFlowNode n | n.getNode() = a)

View File

@@ -3,6 +3,6 @@ private import LegacyPointsTo
from ControlFlowNode f, PointsToContext ctx, Value obj, ControlFlowNode orig
where
exists(ExprStmt s | s.getValue().getAFlowNode() = f) and
exists(ExprStmt s | f.getNode() = s.getValue()) and
PointsTo::pointsTo(f, ctx, obj, orig)
select ctx, f, obj.toString(), orig

View File

@@ -4,6 +4,6 @@ import semmle.python.objects.ObjectInternal
from ControlFlowNode f, ObjectInternal obj, ControlFlowNode orig
where
exists(ExprStmt s | s.getValue().getAFlowNode() = f) and
exists(ExprStmt s | f.getNode() = s.getValue()) and
PointsTo::pointsTo(f, _, obj, orig)
select f, obj.toString(), orig

View File

@@ -43,7 +43,7 @@ query predicate test_taint(string arg_location, string test_res, string scope_na
// TODO: Replace with `hasFlowToExpr` once that is working
if
TestTaintTrackingFlow::flowTo(any(DataFlow::Node n |
n.(DataFlow::CfgNode).getNode() = arg.getAFlowNode()
n.(DataFlow::CfgNode).getNode().getNode() = arg
))
then has_taint = true
else has_taint = false