From ac5fa629ef495c8d5d70b270b4d65ad34eb1481c Mon Sep 17 00:00:00 2001 From: yoff Date: Tue, 2 Jun 2026 08:24:17 +0000 Subject: [PATCH 1/5] Python: inline init_module_submodule_defn into ImportResolution The new-dataflow ImportResolution module only used semmle.python.essa.SsaDefinitions for the 5-line helper predicate SsaSource::init_module_submodule_defn. Inline it locally and drop the dependency on legacy SsaDefinitions. This is the only remaining direct import of semmle.python.essa.* in the new dataflow stack, so dropping it makes the layering cleaner. Semantic noop on the current SSA: SsaSourceVariable.getName() and GlobalVariable.getId() both project the same DB column (variable(_,_,result)), and the old call's 'init.getEntryNode() = f' join was just constraining init = package via Scope.getEntryNode()'s functional uniqueness. RA dump of accesses.ql confirms only the expected predicate-rename shuffle; all 70 dataflow + ApiGraphs library tests pass. This factors out commit 8cab5a20f21 from the larger shared-CFG migration #21925. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../dataflow/new/internal/ImportResolution.qll | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll b/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll index f3943f53f8606..f62c4efcac8a6 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll @@ -9,7 +9,19 @@ private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.ImportStar private import semmle.python.dataflow.new.TypeTracking private import semmle.python.dataflow.new.internal.DataFlowPrivate -private import semmle.python.essa.SsaDefinitions + +/** + * Holds if `init` is a package's `__init__.py` and `var` is a global variable in + * `init` whose name matches a submodule of the package. + * + * Inlined from `SsaSource::init_module_submodule_defn` to avoid pulling + * `semmle.python.essa.SsaDefinitions` into the new dataflow stack. + */ +private predicate initModuleSubmoduleDefn(GlobalVariable var, Module init) { + init.isPackageInit() and + exists(init.getPackage().getSubModule(var.getId())) and + var.getScope() = init +} /** * Python modules and the way imports are resolved are... complicated. Here's a crash course in how @@ -326,7 +338,7 @@ module ImportResolution { // imported yet. exists(string submodule, Module package, EssaVariable var | submodule = var.getName() and - SsaSource::init_module_submodule_defn(var.getSourceVariable(), package.getEntryNode()) and + initModuleSubmoduleDefn(var.getSourceVariable(), package) and m = getModuleFromName(package.getPackageName() + "." + submodule) and result.asCfgNode() = var.getDefinition().(EssaNodeDefinition).getDefiningNode() ) From daea0b4dd3e3200e11d2f510ca379c7457614f61 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 10:53:39 +0000 Subject: [PATCH 2/5] Python: remove AstNode.getAFlowNode() and rewrite callers Preparatory refactor for the shared-CFG dataflow migration. Removes the AstNode.getAFlowNode() cached predicate from the public Python QL API. All ~140 callers across lib/, src/, test/, and tools/ are rewritten from `expr.getAFlowNode() = cfgNode` to `cfgNode.getNode() = expr`, using ControlFlowNode.getNode() which already exists in Flow.qll. 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> --- python/ql/lib/LegacyPointsTo.qll | 33 ++++++++++++++----- python/ql/lib/analysis/DefinitionTracking.qll | 4 +-- .../2026-05-19-remove-getAFlowNode.md | 4 +++ python/ql/lib/semmle/python/AstExtended.qll | 11 ------- python/ql/lib/semmle/python/Exprs.qll | 18 ++-------- python/ql/lib/semmle/python/Flow.qll | 26 +++++++-------- python/ql/lib/semmle/python/Import.qll | 2 -- python/ql/lib/semmle/python/SelfAttribute.qll | 17 ++++++---- .../python/dataflow/new/BarrierGuards.qll | 30 ++++++++++------- .../dataflow/new/internal/Attributes.qll | 2 +- .../new/internal/DataFlowDispatch.qll | 6 ++-- .../dataflow/new/internal/DataFlowPrivate.qll | 4 +-- .../dataflow/new/internal/DataFlowPublic.qll | 2 +- .../dataflow/new/internal/VariableCapture.qll | 4 +-- .../python/dataflow/old/Implementation.qll | 14 ++++---- .../lib/semmle/python/essa/SsaDefinitions.qll | 6 ++-- .../lib/semmle/python/frameworks/Bottle.qll | 2 +- .../lib/semmle/python/frameworks/FastApi.qll | 2 +- .../ql/lib/semmle/python/frameworks/Flask.qll | 4 +-- .../semmle/python/frameworks/FlaskAdmin.qll | 4 +-- .../semmle/python/internal/CachedStages.qll | 2 +- .../lib/semmle/python/objects/Callables.qll | 10 +++--- .../ql/lib/semmle/python/objects/Classes.qll | 5 +-- .../ql/lib/semmle/python/objects/TObject.qll | 2 +- .../lib/semmle/python/pointsto/PointsTo.qll | 14 +++++--- .../lib/semmle/python/types/ClassObject.qll | 7 ++-- .../ql/lib/semmle/python/types/Exceptions.qll | 2 +- .../ql/lib/semmle/python/types/Extensions.qll | 4 +-- .../semmle/python/types/FunctionObject.qll | 2 +- python/ql/lib/semmle/python/types/Object.qll | 6 ++-- python/ql/src/Classes/ClassAttributes.qll | 22 ++++++++----- .../src/Exceptions/CatchingBaseException.ql | 4 ++- python/ql/src/Expressions/CallArgs.qll | 16 +++++---- .../DuplicateKeyInDictionaryLiteral.ql | 10 ++++-- .../Formatting/AdvancedFormatting.qll | 8 +++-- .../Expressions/IncorrectComparisonUsingIs.ql | 2 +- python/ql/src/Expressions/IsComparisons.qll | 2 +- .../ql/src/Expressions/TruncatedDivision.ql | 2 +- .../ql/src/Functions/ExplicitReturnInInit.ql | 4 ++- python/ql/src/Functions/ReturnValueIgnored.ql | 7 +++- python/ql/src/Resources/FileOpen.qll | 4 +-- .../Security/CWE-798/HardcodedCredentials.ql | 2 +- .../Statements/IterableStringOrSequence.ql | 2 +- .../NestedLoopsSameVariableWithReuse.ql | 2 +- .../ql/src/Statements/NonIteratorInForLoop.ql | 2 +- .../ql/src/Statements/SideEffectInAssert.ql | 12 ++++--- python/ql/src/Variables/Definition.qll | 6 +++- .../src/Variables/LeakingListComprehension.ql | 13 +++++--- python/ql/src/Variables/Loop.qll | 7 ++-- python/ql/src/Variables/MultiplyDefined.ql | 4 +-- .../SuspiciousUnusedLoopIterationVariable.ql | 4 ++- python/ql/src/Variables/Undefined.qll | 5 +-- python/ql/src/Variables/UndefinedGlobal.ql | 10 +++--- .../ql/src/Variables/UndefinedPlaceHolder.ql | 4 +-- .../ql/src/Variables/UnusedModuleVariable.ql | 2 +- .../src/analysis/CrossProjectDefinitions.qll | 8 +++-- python/ql/src/analysis/ImportFailure.ql | 7 ++-- python/ql/src/analysis/KeyPointsToFailure.ql | 4 +-- python/ql/src/analysis/PointsToFailure.ql | 2 +- ...onOfParameterWithDefaultCustomizations.qll | 2 +- .../comprehensions/ConsistencyCheck.ql | 4 ++- .../import-resolution/importflow.ql | 8 +++-- .../PointsToSupport/UseFromDefinition.ql | 2 +- .../ControlFlow/splitting/NodeCount.ql | 2 +- .../library-tests/PointsTo/global/Global.ql | 2 +- .../PointsTo/local/LocalPointsTo.ql | 2 +- .../dataflow/tainttracking/TestTaintLib.qll | 2 +- .../ql/lib/RecordedCalls.qll | 5 +-- 68 files changed, 257 insertions(+), 198 deletions(-) create mode 100644 python/ql/lib/change-notes/2026-05-19-remove-getAFlowNode.md diff --git a/python/ql/lib/LegacyPointsTo.qll b/python/ql/lib/LegacyPointsTo.qll index ffea2d93b66c7..f5ad67a3c5553 100644 --- a/python/ql/lib/LegacyPointsTo.qll +++ b/python/ql/lib/LegacyPointsTo.qll @@ -213,9 +213,11 @@ class ExprWithPointsTo extends Expr { * Gets what this expression might "refer-to" in the given `context`. */ predicate refersTo(Context context, Object obj, ClassObject cls, AstNode origin) { - this.getAFlowNode() - .(ControlFlowNodeWithPointsTo) - .refersTo(context, obj, cls, origin.getAFlowNode()) + exists(ControlFlowNode this_, ControlFlowNode origin_ | + this_.getNode() = this and origin_.getNode() = origin + | + this_.(ControlFlowNodeWithPointsTo).refersTo(context, obj, cls, origin_) + ) } /** @@ -226,7 +228,11 @@ class ExprWithPointsTo extends Expr { */ pragma[nomagic] predicate refersTo(Object obj, AstNode origin) { - this.getAFlowNode().(ControlFlowNodeWithPointsTo).refersTo(obj, origin.getAFlowNode()) + exists(ControlFlowNode this_, ControlFlowNode origin_ | + this_.getNode() = this and origin_.getNode() = origin + | + this_.(ControlFlowNodeWithPointsTo).refersTo(obj, origin_) + ) } /** @@ -240,16 +246,22 @@ class ExprWithPointsTo extends Expr { * in the given `context`. */ predicate pointsTo(Context context, Value value, AstNode origin) { - this.getAFlowNode() - .(ControlFlowNodeWithPointsTo) - .pointsTo(context, value, origin.getAFlowNode()) + exists(ControlFlowNode this_, ControlFlowNode origin_ | + this_.getNode() = this and origin_.getNode() = origin + | + this_.(ControlFlowNodeWithPointsTo).pointsTo(context, value, origin_) + ) } /** * Holds if this expression might "point-to" to `value` which is from `origin`. */ predicate pointsTo(Value value, AstNode origin) { - this.getAFlowNode().(ControlFlowNodeWithPointsTo).pointsTo(value, origin.getAFlowNode()) + exists(ControlFlowNode this_, ControlFlowNode origin_ | + this_.getNode() = this and origin_.getNode() = origin + | + this_.(ControlFlowNodeWithPointsTo).pointsTo(value, origin_) + ) } /** @@ -475,7 +487,10 @@ class FunctionMetricsWithPointsTo extends FunctionMetrics { not non_coupling_method(result) and exists(Call call | call.getScope() = this | exists(FunctionObject callee | callee.getFunction() = result | - call.getAFlowNode().getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee) + exists(CallNode call_ | + call_.getNode() = call and + call_.getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee) + ) ) or exists(Attribute a | call.getFunc() = a | diff --git a/python/ql/lib/analysis/DefinitionTracking.qll b/python/ql/lib/analysis/DefinitionTracking.qll index 21155970375b8..583a7807ff27d 100644 --- a/python/ql/lib/analysis/DefinitionTracking.qll +++ b/python/ql/lib/analysis/DefinitionTracking.qll @@ -64,7 +64,7 @@ private predicate jump_to_defn(ControlFlowNode use, Definition defn) { private predicate preferred_jump_to_defn(Expr use, Definition def) { not use instanceof ClassExpr and not use instanceof FunctionExpr and - jump_to_defn(use.getAFlowNode(), def) + exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, def)) } private predicate unique_jump_to_defn(Expr use, Definition def) { @@ -452,7 +452,7 @@ private predicate self_parameter_jump_to_defn_attribute( * This exists primarily for testing use `getPreferredDefinition()` instead. */ Definition getADefinition(Expr use) { - jump_to_defn(use.getAFlowNode(), result) and + exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, result)) and not use instanceof Call and not use.isArtificial() and // Not the use itself diff --git a/python/ql/lib/change-notes/2026-05-19-remove-getAFlowNode.md b/python/ql/lib/change-notes/2026-05-19-remove-getAFlowNode.md new file mode 100644 index 0000000000000..82cedf579047d --- /dev/null +++ b/python/ql/lib/change-notes/2026-05-19-remove-getAFlowNode.md @@ -0,0 +1,4 @@ +--- +category: breaking +--- +* The `AstNode.getAFlowNode()` predicate has been removed. Use `ControlFlowNode.getNode()` from the other direction instead: replace `e.getAFlowNode() = n` with `n.getNode() = e`. This is a preparatory refactor for migrating the dataflow library off the legacy CFG; it has no semantic effect. diff --git a/python/ql/lib/semmle/python/AstExtended.qll b/python/ql/lib/semmle/python/AstExtended.qll index 13da4e899a71e..32b9ce6eee7cf 100644 --- a/python/ql/lib/semmle/python/AstExtended.qll +++ b/python/ql/lib/semmle/python/AstExtended.qll @@ -16,17 +16,6 @@ abstract class AstNode extends AstNode_ { /** Gets the scope that this node occurs in */ abstract Scope getScope(); - /** - * Gets a flow node corresponding directly to this node. - * NOTE: For some statements and other purely syntactic elements, - * there may not be a `ControlFlowNode` - */ - cached - ControlFlowNode getAFlowNode() { - Stages::AST::ref() and - py_flow_bb_node(result, this, _, _) - } - /** Gets the location for this AST node */ cached Location getLocation() { none() } diff --git a/python/ql/lib/semmle/python/Exprs.qll b/python/ql/lib/semmle/python/Exprs.qll index 6ab9f8d8340dd..9ce6f9e6680aa 100644 --- a/python/ql/lib/semmle/python/Exprs.qll +++ b/python/ql/lib/semmle/python/Exprs.qll @@ -28,7 +28,9 @@ class Expr extends Expr_, AstNode { /** Whether this expression may have a side effect (as determined purely from its syntax) */ predicate hasSideEffects() { /* If an exception raised by this expression handled, count that as a side effect */ - this.getAFlowNode().getASuccessor().getNode() instanceof ExceptStmt + exists(ControlFlowNode n | n.getNode() = this | + n.getASuccessor().getNode() instanceof ExceptStmt + ) or this.getASubExpression().hasSideEffects() } @@ -68,8 +70,6 @@ class Attribute extends Attribute_ { /* syntax: Expr.name */ override Expr getASubExpression() { result = this.getObject() } - override AttrNode getAFlowNode() { result = super.getAFlowNode() } - /** Gets the name of this attribute. That is the `name` in `obj.name` */ string getName() { result = Attribute_.super.getAttr() } @@ -96,8 +96,6 @@ class Subscript extends Subscript_ { } Expr getObject() { result = Subscript_.super.getValue() } - - override SubscriptNode getAFlowNode() { result = super.getAFlowNode() } } /** A call expression, such as `func(...)` */ @@ -113,8 +111,6 @@ class Call extends Call_ { override string toString() { result = this.getFunc().toString() + "()" } - override CallNode getAFlowNode() { result = super.getAFlowNode() } - /** Gets a tuple (*) argument of this call. */ Expr getStarargs() { result = this.getAPositionalArg().(Starred).getValue() } @@ -200,8 +196,6 @@ class IfExp extends IfExp_ { override Expr getASubExpression() { result = this.getTest() or result = this.getBody() or result = this.getOrelse() } - - override IfExprNode getAFlowNode() { result = super.getAFlowNode() } } /** A starred expression, such as the `*rest` in the assignment `first, *rest = seq` */ @@ -410,8 +404,6 @@ class PlaceHolder extends PlaceHolder_ { override Expr getASubExpression() { none() } override string toString() { result = "$" + this.getId() } - - override NameNode getAFlowNode() { result = super.getAFlowNode() } } /** A tuple expression such as `( 1, 3, 5, 7, 9 )` */ @@ -478,8 +470,6 @@ class Name extends Name_ { override string toString() { result = this.getId() } - override NameNode getAFlowNode() { result = super.getAFlowNode() } - override predicate isArtificial() { /* Artificial variable names in comprehensions all start with "." */ this.getId().charAt(0) = "." @@ -585,8 +575,6 @@ abstract class NameConstant extends Name, ImmutableLiteral { override predicate isConstant() { any() } - override NameConstantNode getAFlowNode() { result = Name.super.getAFlowNode() } - override predicate isArtificial() { none() } } diff --git a/python/ql/lib/semmle/python/Flow.qll b/python/ql/lib/semmle/python/Flow.qll index 94caf513aa983..bdb01f4c9e2e7 100644 --- a/python/ql/lib/semmle/python/Flow.qll +++ b/python/ql/lib/semmle/python/Flow.qll @@ -555,27 +555,27 @@ class DefinitionNode extends ControlFlowNode { cached DefinitionNode() { Stages::AST::ref() and - exists(Assign a | a.getATarget().getAFlowNode() = this) + exists(Assign a | this.getNode() = a.getATarget()) or - exists(AssignExpr a | a.getTarget().getAFlowNode() = this) + exists(AssignExpr a | this.getNode() = a.getTarget()) or - exists(AnnAssign a | a.getTarget().getAFlowNode() = this and exists(a.getValue())) + exists(AnnAssign a | this.getNode() = a.getTarget() and exists(a.getValue())) or - exists(Alias a | a.getAsname().getAFlowNode() = this) + exists(Alias a | this.getNode() = a.getAsname()) or augstore(_, this) or // `x, y = 1, 2` where LHS is a combination of list or tuples - exists(Assign a | list_or_tuple_nested_element(a.getATarget()).getAFlowNode() = this) + exists(Assign a | this.getNode() = list_or_tuple_nested_element(a.getATarget())) or - exists(For for | for.getTarget().getAFlowNode() = this) + exists(For for | this.getNode() = for.getTarget()) or - exists(Parameter param | this = param.asName().getAFlowNode() and exists(param.getDefault())) + exists(Parameter param | this.getNode() = param.asName() and exists(param.getDefault())) } /** flow node corresponding to the value assigned for the definition corresponding to this flow node */ ControlFlowNode getValue() { - result = assigned_value(this.getNode()).getAFlowNode() and + result.getNode() = assigned_value(this.getNode()) and ( result.getBasicBlock().dominates(this.getBasicBlock()) or @@ -584,7 +584,7 @@ class DefinitionNode extends ControlFlowNode { // since the default value for a parameter is evaluated in the same basic block as // the function definition, but the parameter belongs to the basic block of the function, // there is no dominance relationship between the two. - exists(Parameter param | this = param.asName().getAFlowNode()) + exists(Parameter param | this.getNode() = param.asName()) ) } } @@ -901,7 +901,7 @@ class ExceptFlowNode extends ControlFlowNode { exists(ExceptStmt ex | this.getBasicBlock().dominates(result.getBasicBlock()) and ex = this.getNode() and - result = ex.getType().getAFlowNode() + result.getNode() = ex.getType() ) } @@ -913,7 +913,7 @@ class ExceptFlowNode extends ControlFlowNode { exists(ExceptStmt ex | this.getBasicBlock().dominates(result.getBasicBlock()) and ex = this.getNode() and - result = ex.getName().getAFlowNode() + result.getNode() = ex.getName() ) } } @@ -928,7 +928,7 @@ class ExceptGroupFlowNode extends ControlFlowNode { */ ControlFlowNode getType() { this.getBasicBlock().dominates(result.getBasicBlock()) and - result = this.getNode().(ExceptGroupStmt).getType().getAFlowNode() + result.getNode() = this.getNode().(ExceptGroupStmt).getType() } /** @@ -937,7 +937,7 @@ class ExceptGroupFlowNode extends ControlFlowNode { */ ControlFlowNode getName() { this.getBasicBlock().dominates(result.getBasicBlock()) and - result = this.getNode().(ExceptGroupStmt).getName().getAFlowNode() + result.getNode() = this.getNode().(ExceptGroupStmt).getName() } } diff --git a/python/ql/lib/semmle/python/Import.qll b/python/ql/lib/semmle/python/Import.qll index 2f7fae9553998..d4f5109ed47c9 100644 --- a/python/ql/lib/semmle/python/Import.qll +++ b/python/ql/lib/semmle/python/Import.qll @@ -162,8 +162,6 @@ class ImportMember extends ImportMember_ { string getImportedModuleName() { result = this.getModule().(ImportExpr).getImportedModuleName() + "." + this.getName() } - - override ImportMemberNode getAFlowNode() { result = super.getAFlowNode() } } /** An import statement */ diff --git a/python/ql/lib/semmle/python/SelfAttribute.qll b/python/ql/lib/semmle/python/SelfAttribute.qll index 90ef2b38401ab..364e080dcdd77 100644 --- a/python/ql/lib/semmle/python/SelfAttribute.qll +++ b/python/ql/lib/semmle/python/SelfAttribute.qll @@ -46,20 +46,23 @@ class SelfAttributeRead extends SelfAttribute { } predicate guardedByHasattr() { - exists(Variable var, ControlFlowNode n | - var.getAUse() = this.getObject().getAFlowNode() and + exists(Variable var, ControlFlowNode n, ControlFlowNode this_, ControlFlowNode obj_ | + this_.getNode() = this and obj_.getNode() = this.getObject() + | + var.getAUse() = obj_ and hasattr(n, var.getAUse(), this.getName()) and - n.strictlyDominates(this.getAFlowNode()) + n.strictlyDominates(this_) ) } pragma[noinline] predicate locallyDefined() { - exists(SelfAttributeStore store | - this.getName() = store.getName() and - this.getScope() = store.getScope() + exists(SelfAttributeStore store, ControlFlowNode store_, ControlFlowNode this_ | + store_.getNode() = store and this_.getNode() = this | - store.getAFlowNode().strictlyDominates(this.getAFlowNode()) + this.getName() = store.getName() and + this.getScope() = store.getScope() and + store_.strictlyDominates(this_) ) } } diff --git a/python/ql/lib/semmle/python/dataflow/new/BarrierGuards.qll b/python/ql/lib/semmle/python/dataflow/new/BarrierGuards.qll index fefa30965cec5..072098991bb4f 100644 --- a/python/ql/lib/semmle/python/dataflow/new/BarrierGuards.qll +++ b/python/ql/lib/semmle/python/dataflow/new/BarrierGuards.qll @@ -5,24 +5,30 @@ private import semmle.python.dataflow.new.DataFlow private predicate constCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { exists(CompareNode cn | cn = g | - exists(ImmutableLiteral const, Cmpop op | - op = any(Eq eq) and branch = true - or - op = any(NotEq ne) and branch = false + exists(ImmutableLiteral const, Cmpop op, ControlFlowNode c | + c.getNode() = const and + ( + op = any(Eq eq) and branch = true + or + op = any(NotEq ne) and branch = false + ) | - cn.operands(const.getAFlowNode(), op, node) + cn.operands(c, op, node) or - cn.operands(node, op, const.getAFlowNode()) + cn.operands(node, op, c) ) or - exists(NameConstant const, Cmpop op | - op = any(Is is_) and branch = true - or - op = any(IsNot isn) and branch = false + exists(NameConstant const, Cmpop op, ControlFlowNode c | + c.getNode() = const and + ( + op = any(Is is_) and branch = true + or + op = any(IsNot isn) and branch = false + ) | - cn.operands(const.getAFlowNode(), op, node) + cn.operands(c, op, node) or - cn.operands(node, op, const.getAFlowNode()) + cn.operands(node, op, c) ) or exists(IterableNode const_iterable, Cmpop op | diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll b/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll index 8778ae288667d..76d2cb11e1447 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll @@ -228,7 +228,7 @@ private class ClassDefinitionAsAttrWrite extends AttrWrite, CfgNode { override Node getValue() { result.asCfgNode() = node.getValue() } - override Node getObject() { result.asCfgNode() = cls.getAFlowNode() } + override Node getObject() { result.asCfgNode().getNode() = cls } override ExprNode getAttributeNameExpr() { none() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll index 4e3a011e8d101..23b49882445e5 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll @@ -1913,8 +1913,8 @@ abstract class ReturnNode extends Node { class ExtractedReturnNode extends ReturnNode, CfgNode { // See `TaintTrackingImplementation::returnFlowStep` ExtractedReturnNode() { - node = any(Return ret).getValue().getAFlowNode() or - node = any(Yield yield).getAFlowNode() + node.getNode() = any(Return ret).getValue() or + node.getNode() = any(Yield yield) } override ReturnKind getKind() { any() } @@ -1932,7 +1932,7 @@ class ExtractedReturnNode extends ReturnNode, CfgNode { class YieldNodeInContextManagerFunction extends ReturnNode, CfgNode { YieldNodeInContextManagerFunction() { hasContextmanagerDecorator(node.getScope()) and - node = any(Yield yield).getValue().getAFlowNode() + node.getNode() = any(Yield yield).getValue() } override ReturnKind getKind() { any() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll index fffd0150008eb..67963e7cd382b 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll @@ -185,8 +185,8 @@ private predicate synthDictSplatArgumentNodeStoreStep( */ predicate yieldStoreStep(Node nodeFrom, Content c, Node nodeTo) { exists(Yield yield | - nodeTo.asCfgNode() = yield.getAFlowNode() and - nodeFrom.asCfgNode() = yield.getValue().getAFlowNode() and + nodeTo.asCfgNode().getNode() = yield and + nodeFrom.asCfgNode().getNode() = yield.getValue() and // TODO: Consider if this will also need to transfer dictionary content // once dictionary comprehensions are supported. c instanceof ListElementContent diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll index 8612d4a253e07..a9d73fe0527fe 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll @@ -485,7 +485,7 @@ class ModuleVariableNode extends Node, TModuleVariableNode { /** Gets a node that reads this variable, excluding reads that happen through `from ... import *`. */ Node getALocalRead() { - result.asCfgNode() = var.getALoad().getAFlowNode() and + result.asCfgNode().getNode() = var.getALoad() and not result.getScope() = mod } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll b/python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll index fbe05979328c5..b170f7d95d795 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll @@ -61,7 +61,7 @@ private module CaptureInput implements Shared::InputSig limit @@ -211,7 +215,7 @@ predicate too_many_args(Call call, Value callable, int limit) { call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1 or callable instanceof ClassValue and - call.getAFlowNode() = get_a_call(callable) and + exists(ControlFlowNode callCfg | callCfg.getNode() = call | callCfg = get_a_call(callable)) and limit = func.maxParameters() - 1 ) and positional_arg_count_for_call(call, callable) > limit diff --git a/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql index 166eae635fade..0c55a2ece587b 100644 --- a/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql +++ b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql @@ -36,11 +36,15 @@ where exists(string s | dict_key(d, k1, s) and dict_key(d, k2, s) and k1 != k2) and ( exists(BasicBlock b, int i1, int i2 | - k1.getAFlowNode() = b.getNode(i1) and - k2.getAFlowNode() = b.getNode(i2) and + b.getNode(i1).getNode() = k1 and + b.getNode(i2).getNode() = k2 and i1 < i2 ) or - k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock()) + exists(ControlFlowNode k1Cfg, ControlFlowNode k2Cfg | + k1Cfg.getNode() = k1 and k2Cfg.getNode() = k2 + | + k1Cfg.getBasicBlock().strictlyDominates(k2Cfg.getBasicBlock()) + ) ) select k1, "Dictionary key " + repr(k1) + " is subsequently $@.", k2, "overwritten" diff --git a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll index d98286d85faf3..a5e0379685a39 100644 --- a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll +++ b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll @@ -98,16 +98,18 @@ private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int en } private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) { - exists(CallNode call | call = format_expr.getAFlowNode() | + exists(CallNode call, ControlFlowNode fmtCfg | + call.getNode() = format_expr and fmtCfg.getNode() = fmt + | call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(Value::named("format")) and - call.getArg(0).(ControlFlowNodeWithPointsTo).pointsTo(_, fmt.getAFlowNode()) and + call.getArg(0).(ControlFlowNodeWithPointsTo).pointsTo(_, fmtCfg) and args = count(format_expr.getAnArg()) - 1 or call.getFunction() .(AttrNode) .getObject("format") .(ControlFlowNodeWithPointsTo) - .pointsTo(_, fmt.getAFlowNode()) and + .pointsTo(_, fmtCfg) and args = count(format_expr.getAnArg()) ) } diff --git a/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql b/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql index fa0ca14669f68..a7336c625472f 100644 --- a/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql +++ b/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql @@ -15,7 +15,7 @@ import python /** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */ predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) { - exists(CompareNode fcomp | fcomp = comp.getAFlowNode() | + exists(CompareNode fcomp | fcomp.getNode() = comp | fcomp.operands(left, op, right) and (op instanceof Is or op instanceof IsNot) ) diff --git a/python/ql/src/Expressions/IsComparisons.qll b/python/ql/src/Expressions/IsComparisons.qll index cb052ceca765b..ee49f6c3337a2 100644 --- a/python/ql/src/Expressions/IsComparisons.qll +++ b/python/ql/src/Expressions/IsComparisons.qll @@ -5,7 +5,7 @@ private import LegacyPointsTo /** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */ predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) { - exists(CompareNode fcomp | fcomp = comp.getAFlowNode() | + exists(CompareNode fcomp | fcomp.getNode() = comp | fcomp.operands(left, op, right) and (op instanceof Is or op instanceof IsNot) ) diff --git a/python/ql/src/Expressions/TruncatedDivision.ql b/python/ql/src/Expressions/TruncatedDivision.ql index c731a21f7d268..d63ac056d3c2a 100644 --- a/python/ql/src/Expressions/TruncatedDivision.ql +++ b/python/ql/src/Expressions/TruncatedDivision.ql @@ -19,7 +19,7 @@ where // Only relevant for Python 2, as all later versions implement true division major_version() = 2 and exists(BinaryExprNode bin, Value lval, Value rval | - bin = div.getAFlowNode() and + bin.getNode() = div and bin.getNode().getOp() instanceof Div and bin.getLeft().(ControlFlowNodeWithPointsTo).pointsTo(lval, left) and lval.getClass() = ClassValue::int_() and diff --git a/python/ql/src/Functions/ExplicitReturnInInit.ql b/python/ql/src/Functions/ExplicitReturnInInit.ql index f1300afbfd0a8..25fc799fafae8 100644 --- a/python/ql/src/Functions/ExplicitReturnInInit.ql +++ b/python/ql/src/Functions/ExplicitReturnInInit.ql @@ -19,7 +19,9 @@ where exists(Function init | init.isInitMethod() and r.getScope() = init) and r.getValue() = rv and not rv.pointsTo(Value::none_()) and - not exists(FunctionValue f | f.getACall() = rv.getAFlowNode() | f.neverReturns()) and + not exists(FunctionValue f, ControlFlowNode rvCfg | rvCfg.getNode() = rv | + f.getACall() = rvCfg and f.neverReturns() + ) and // to avoid double reporting, don't trigger if returning result from other __init__ function not exists(Attribute meth | meth = rv.(Call).getFunc() | meth.getName() = "__init__") select r, "Explicit return in __init__ method." diff --git a/python/ql/src/Functions/ReturnValueIgnored.ql b/python/ql/src/Functions/ReturnValueIgnored.ql index 3716b989d891d..83af6304cb309 100644 --- a/python/ql/src/Functions/ReturnValueIgnored.ql +++ b/python/ql/src/Functions/ReturnValueIgnored.ql @@ -69,7 +69,12 @@ where returns_meaningful_value(callee) and not wrapped_in_try_except(call) and exists(int unused | - unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and + unused = + count(ExprStmt e | + exists(ControlFlowNode eValCfg | eValCfg.getNode() = e.getValue() | + eValCfg = callee.getACall() + ) + ) and total = count(callee.getACall()) | percentage_used = (100.0 * (total - unused) / total).floor() diff --git a/python/ql/src/Resources/FileOpen.qll b/python/ql/src/Resources/FileOpen.qll index dd952e732d429..1daecb6d0334e 100644 --- a/python/ql/src/Resources/FileOpen.qll +++ b/python/ql/src/Resources/FileOpen.qll @@ -138,12 +138,12 @@ predicate function_opens_file(FunctionValue f) { f = Value::named("open") or exists(EssaVariable v, Return ret | ret.getScope() = f.getScope() | - ret.getValue().getAFlowNode() = v.getAUse() and + v.getNode() = ret.getValue().getAUse() and var_is_open(v, _) ) or exists(Return ret, FunctionValue callee | ret.getScope() = f.getScope() | - ret.getValue().getAFlowNode() = callee.getACall() and + callee.getNode() = ret.getValue().getACall() and function_opens_file(callee) ) } diff --git a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql index 1e7b4452a9a61..baeaf26514c45 100644 --- a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql +++ b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql @@ -94,7 +94,7 @@ class CredentialSink extends DataFlow::Node { this.(DataFlow::ArgumentNode).argumentOf(_, pos) ) or - exists(Keyword k | k.getArg() = name and k.getValue().getAFlowNode() = this.asCfgNode()) + exists(Keyword k | k.getArg() = name and this.asCfgNode().getNode() = k.getValue()) or exists(CompareNode cmp, NameNode n | n.getId() = name | cmp.operands(this.asCfgNode(), any(Eq eq), n) diff --git a/python/ql/src/Statements/IterableStringOrSequence.ql b/python/ql/src/Statements/IterableStringOrSequence.ql index d1c4a507f0d19..ad8b6beab2907 100644 --- a/python/ql/src/Statements/IterableStringOrSequence.ql +++ b/python/ql/src/Statements/IterableStringOrSequence.ql @@ -25,7 +25,7 @@ from For loop, ControlFlowNodeWithPointsTo iter, Value str, Value seq, ControlFlowNode seq_origin, ControlFlowNode str_origin where - loop.getIter().getAFlowNode() = iter and + iter.getNode() = loop.getIter() and iter.pointsTo(str, str_origin) and iter.pointsTo(seq, seq_origin) and has_string_type(str) and diff --git a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql index c4deb4e642777..a9c5a5fbbd982 100644 --- a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql +++ b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql @@ -15,7 +15,7 @@ import python predicate loop_variable_ssa(For f, Variable v, SsaVariable s) { - f.getTarget().getAFlowNode() = s.getDefinition() and v = s.getVariable() + s.getDefinition().getNode() = f.getTarget() and v = s.getVariable() } predicate variableUsedInNestedLoops(For inner, For outer, Variable v, Name n) { diff --git a/python/ql/src/Statements/NonIteratorInForLoop.ql b/python/ql/src/Statements/NonIteratorInForLoop.ql index f8e6e51b55ffc..b0cbc71130d03 100644 --- a/python/ql/src/Statements/NonIteratorInForLoop.ql +++ b/python/ql/src/Statements/NonIteratorInForLoop.ql @@ -16,7 +16,7 @@ private import LegacyPointsTo from For loop, ControlFlowNodeWithPointsTo iter, Value v, ClassValue t, ControlFlowNode origin where - loop.getIter().getAFlowNode() = iter and + iter.getNode() = loop.getIter() and iter.pointsTo(_, v, origin) and v.getClass() = t and not t.isIterable() and diff --git a/python/ql/src/Statements/SideEffectInAssert.ql b/python/ql/src/Statements/SideEffectInAssert.ql index 7ac96030c04eb..55c34144dced3 100644 --- a/python/ql/src/Statements/SideEffectInAssert.ql +++ b/python/ql/src/Statements/SideEffectInAssert.ql @@ -24,11 +24,13 @@ predicate func_with_side_effects(Expr e) { } predicate call_with_side_effect(Call e) { - e.getAFlowNode() = - API::moduleImport("subprocess") - .getMember(["call", "check_call", "check_output"]) - .getACall() - .asCfgNode() + exists(ControlFlowNode eCfg | eCfg.getNode() = e | + eCfg = + API::moduleImport("subprocess") + .getMember(["call", "check_call", "check_output"]) + .getACall() + .asCfgNode() + ) } predicate probable_side_effect(Expr e) { diff --git a/python/ql/src/Variables/Definition.qll b/python/ql/src/Variables/Definition.qll index be8c9490788cf..9bd7130957b68 100644 --- a/python/ql/src/Variables/Definition.qll +++ b/python/ql/src/Variables/Definition.qll @@ -133,7 +133,11 @@ class ListComprehensionDeclaration extends ListComp { major_version() = 2 and this.getIterationVariable(_).getId() = result.getId() and result.getScope() = this.getScope() and - this.getAFlowNode().strictlyReaches(result.getAFlowNode()) and + exists(ControlFlowNode thisCfg, ControlFlowNode resultCfg | + thisCfg.getNode() = this and resultCfg.getNode() = result + | + thisCfg.strictlyReaches(resultCfg) + ) and result.isUse() } diff --git a/python/ql/src/Variables/LeakingListComprehension.ql b/python/ql/src/Variables/LeakingListComprehension.ql index 9b98fb43a313f..a9baa21661da1 100644 --- a/python/ql/src/Variables/LeakingListComprehension.ql +++ b/python/ql/src/Variables/LeakingListComprehension.ql @@ -13,18 +13,21 @@ import python import Definition -from ListComprehensionDeclaration l, Name use, Name defn +from + ListComprehensionDeclaration l, Name use, Name defn, ControlFlowNode lCfg, ControlFlowNode useCfg where use = l.getALeakedVariableUse() and defn = l.getDefinition() and - l.getAFlowNode().strictlyReaches(use.getAFlowNode()) and + lCfg.getNode() = l and + useCfg.getNode() = use and + lCfg.strictlyReaches(useCfg) and /* Make sure we aren't in a loop, as the variable may be redefined */ - not use.getAFlowNode().strictlyReaches(l.getAFlowNode()) and + not useCfg.strictlyReaches(lCfg) and not l.contains(use) and not use.deletes(_) and not exists(SsaVariable v | - v.getAUse() = use.getAFlowNode() and - not v.getDefinition().strictlyDominates(l.getAFlowNode()) + v.getAUse() = useCfg and + not v.getDefinition().strictlyDominates(lCfg) ) select use, use.getId() + " may have a different value in Python 3, as the $@ will not be in scope.", defn, diff --git a/python/ql/src/Variables/Loop.qll b/python/ql/src/Variables/Loop.qll index c7749fe476bf4..e7c189cac354c 100644 --- a/python/ql/src/Variables/Loop.qll +++ b/python/ql/src/Variables/Loop.qll @@ -26,8 +26,11 @@ private Stmt loop_probably_defines(Variable v) { /** Holds if the variable used by `use` is probably defined in a loop */ predicate probably_defined_in_loop(Name use) { - exists(Stmt loop | loop = loop_probably_defines(use.getVariable()) | - loop.getAFlowNode().strictlyReaches(use.getAFlowNode()) + exists(Stmt loop, ControlFlowNode loopCfg, ControlFlowNode useCfg | + loop = loop_probably_defines(use.getVariable()) and + loopCfg.getNode() = loop and + useCfg.getNode() = use and + loopCfg.strictlyReaches(useCfg) ) } diff --git a/python/ql/src/Variables/MultiplyDefined.ql b/python/ql/src/Variables/MultiplyDefined.ql index 3c26ff0b1eb1c..ce8b5b316c218 100644 --- a/python/ql/src/Variables/MultiplyDefined.ql +++ b/python/ql/src/Variables/MultiplyDefined.ql @@ -24,8 +24,8 @@ predicate multiply_defined(AstNode asgn1, AstNode asgn2, Variable v) { forex(Definition def, Definition redef | def.getVariable() = v and - def = asgn1.getAFlowNode() and - redef = asgn2.getAFlowNode() + def.getNode() = asgn1 and + redef.getNode() = asgn2 | def.isUnused() and def.getARedef() = redef and diff --git a/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql index d252742d67c26..f74fd4970ee48 100644 --- a/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql +++ b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql @@ -88,7 +88,9 @@ predicate implicit_repeat(For f) { * E.g. gets `x` from `{ y for y in x }`. */ ControlFlowNode get_comp_iterable(For f) { - exists(Comp c | c.getFunction().getStmt(0) = f | c.getAFlowNode().getAPredecessor() = result) + exists(Comp c, ControlFlowNode cCfg | + c.getFunction().getStmt(0) = f and cCfg.getNode() = c and cCfg.getAPredecessor() = result + ) } from For f, Variable v, string msg diff --git a/python/ql/src/Variables/Undefined.qll b/python/ql/src/Variables/Undefined.qll index 42437a81340b6..b320c2040b2df 100644 --- a/python/ql/src/Variables/Undefined.qll +++ b/python/ql/src/Variables/Undefined.qll @@ -19,9 +19,10 @@ private predicate loop_entry_variables(EssaVariable pred, EssaVariable succ) { private predicate loop_entry_edge(BasicBlock pred, BasicBlock loop) { pred = loop.getAPredecessor() and pred = loop.getImmediateDominator() and - exists(Stmt s | + exists(Stmt s, ControlFlowNode sCfg | loop_probably_executes_at_least_once(s) and - s.getAFlowNode().getBasicBlock() = loop + sCfg.getNode() = s and + sCfg.getBasicBlock() = loop ) } diff --git a/python/ql/src/Variables/UndefinedGlobal.ql b/python/ql/src/Variables/UndefinedGlobal.ql index 404ac64aa5a00..0c54b444ce308 100644 --- a/python/ql/src/Variables/UndefinedGlobal.ql +++ b/python/ql/src/Variables/UndefinedGlobal.ql @@ -27,7 +27,7 @@ predicate guarded_against_name_error(Name u) { | globals.getFunc().(Name).getId() = "globals" and guard.controls(controlled, _) and - controlled.contains(u.getAFlowNode()) + exists(ControlFlowNode uCfg | uCfg.getNode() = u | controlled.contains(uCfg)) ) } @@ -101,18 +101,18 @@ predicate undefined_use(Name u) { } private predicate first_use_in_a_block(Name use) { - exists(GlobalVariable v, BasicBlock b, int i | - i = min(int j | b.getNode(j).getNode() = v.getALoad()) and b.getNode(i) = use.getAFlowNode() + exists(GlobalVariable v, BasicBlock b, int i, ControlFlowNode useCfg | useCfg.getNode() = use | + i = min(int j | b.getNode(j).getNode() = v.getALoad()) and b.getNode(i) = useCfg ) } predicate first_undefined_use(Name use) { undefined_use(use) and - exists(GlobalVariable v | v.getALoad() = use | + exists(GlobalVariable v, ControlFlowNode useCfg | v.getALoad() = use and useCfg.getNode() = use | first_use_in_a_block(use) and not exists(ControlFlowNode other | other.getNode() = v.getALoad() and - other.getBasicBlock().strictlyDominates(use.getAFlowNode().getBasicBlock()) + other.getBasicBlock().strictlyDominates(useCfg.getBasicBlock()) ) ) } diff --git a/python/ql/src/Variables/UndefinedPlaceHolder.ql b/python/ql/src/Variables/UndefinedPlaceHolder.ql index 29f9b3a1a5101..9fa0cc7eaaae9 100644 --- a/python/ql/src/Variables/UndefinedPlaceHolder.ql +++ b/python/ql/src/Variables/UndefinedPlaceHolder.ql @@ -18,8 +18,8 @@ private import semmle.python.types.ImportTime /* Local variable part */ predicate initialized_as_local(PlaceHolder use) { - exists(SsaVariableWithPointsTo l, Function f | - f = use.getScope() and l.getAUse() = use.getAFlowNode() + exists(SsaVariableWithPointsTo l, Function f, ControlFlowNode useCfg | + f = use.getScope() and useCfg.getNode() = use and l.getAUse() = useCfg | l.getVariable() instanceof LocalVariable and not l.maybeUndefined() diff --git a/python/ql/src/Variables/UnusedModuleVariable.ql b/python/ql/src/Variables/UnusedModuleVariable.ql index 24d6559d6fea9..0443c3388c85e 100644 --- a/python/ql/src/Variables/UnusedModuleVariable.ql +++ b/python/ql/src/Variables/UnusedModuleVariable.ql @@ -54,7 +54,7 @@ predicate unused_global(Name unused, GlobalVariable v) { u.uses(v) | // That is reachable from this definition, directly - defn.strictlyReaches(u.getAFlowNode()) + exists(ControlFlowNode uCfg | uCfg.getNode() = u | defn.strictlyReaches(uCfg)) or // indirectly defn.getBasicBlock().reachesExit() and u.getScope() != unused.getScope() diff --git a/python/ql/src/analysis/CrossProjectDefinitions.qll b/python/ql/src/analysis/CrossProjectDefinitions.qll index 64b30f566f15e..61e12a09ec6b9 100644 --- a/python/ql/src/analysis/CrossProjectDefinitions.qll +++ b/python/ql/src/analysis/CrossProjectDefinitions.qll @@ -48,15 +48,17 @@ class Symbol extends TSymbol { AstNode find() { this = TModule(result) or - exists(Symbol s, string name | this = TMember(s, name) | + exists(Symbol s, string name, ControlFlowNode resultCfg | + this = TMember(s, name) and resultCfg.getNode() = result + | exists(ClassObject cls | s.resolvesTo() = cls and - cls.attributeRefersTo(name, _, result.getAFlowNode()) + cls.attributeRefersTo(name, _, resultCfg) ) or exists(ModuleObject m | s.resolvesTo() = m and - m.attributeRefersTo(name, _, result.getAFlowNode()) + m.attributeRefersTo(name, _, resultCfg) ) ) } diff --git a/python/ql/src/analysis/ImportFailure.ql b/python/ql/src/analysis/ImportFailure.ql index 71967e6e04f73..760a3693d6ea3 100644 --- a/python/ql/src/analysis/ImportFailure.ql +++ b/python/ql/src/analysis/ImportFailure.ql @@ -80,10 +80,11 @@ class VersionGuard extends ConditionBlock { VersionGuard() { this.getLastNode() instanceof VersionTest } } -from ImportExpr ie +from ImportExpr ie, ControlFlowNode ieCfg where + ieCfg.getNode() = ie and not ie.(ExprWithPointsTo).refersTo(_) and - exists(Context c | c.appliesTo(ie.getAFlowNode())) and + exists(Context c | c.appliesTo(ieCfg)) and not ok_to_fail(ie) and - not exists(VersionGuard guard | guard.controls(ie.getAFlowNode().getBasicBlock(), _)) + not exists(VersionGuard guard | guard.controls(ieCfg.getBasicBlock(), _)) select ie, "Unable to resolve import of '" + ie.getImportedModuleName() + "'." diff --git a/python/ql/src/analysis/KeyPointsToFailure.ql b/python/ql/src/analysis/KeyPointsToFailure.ql index f07e8638f385f..e42e5ac0bdd4a 100644 --- a/python/ql/src/analysis/KeyPointsToFailure.ql +++ b/python/ql/src/analysis/KeyPointsToFailure.ql @@ -11,13 +11,13 @@ import python import semmle.python.pointsto.PointsTo predicate points_to_failure(Expr e) { - exists(ControlFlowNode f | f = e.getAFlowNode() | not PointsTo::pointsTo(f, _, _, _)) + exists(ControlFlowNode f | f.getNode() = e | not PointsTo::pointsTo(f, _, _, _)) } predicate key_points_to_failure(Expr e) { points_to_failure(e) and not points_to_failure(e.getASubExpression()) and - not exists(SsaVariable ssa | ssa.getAUse() = e.getAFlowNode() | + not exists(SsaVariable ssa, ControlFlowNode eCfg | eCfg.getNode() = e and ssa.getAUse() = eCfg | points_to_failure(ssa.getAnUltimateDefinition().getDefinition().getNode()) ) and not exists(Assign a | a.getATarget() = e) diff --git a/python/ql/src/analysis/PointsToFailure.ql b/python/ql/src/analysis/PointsToFailure.ql index fee1e80d2f77c..8d46cbd90952e 100644 --- a/python/ql/src/analysis/PointsToFailure.ql +++ b/python/ql/src/analysis/PointsToFailure.ql @@ -12,5 +12,5 @@ import python private import LegacyPointsTo from Expr e -where exists(ControlFlowNodeWithPointsTo f | f = e.getAFlowNode() | not f.refersTo(_)) +where exists(ControlFlowNodeWithPointsTo f | f.getNode() = e | not f.refersTo(_)) select e, "Expression does not 'point-to' any object." diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll index c7aef20c09ddf..83ba4df4e2984 100644 --- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll +++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll @@ -131,7 +131,7 @@ module ModificationOfParameterWithDefault { exists(DeletionNode d | d.getTarget().(SubscriptNode).getObject() = this.asCfgNode()) or // augmented assignment to the value - exists(AugAssign a | a.getTarget().getAFlowNode() = this.asCfgNode()) + exists(AugAssign a | this.asCfgNode().getNode() = a.getTarget()) or // modifying function call exists(DataFlow::CallCfgNode c, DataFlow::AttrRead a | c.getFunction() = a | diff --git a/python/ql/test/2/library-tests/comprehensions/ConsistencyCheck.ql b/python/ql/test/2/library-tests/comprehensions/ConsistencyCheck.ql index 2f5191fb5475a..c1a214e40c754 100644 --- a/python/ql/test/2/library-tests/comprehensions/ConsistencyCheck.ql +++ b/python/ql/test/2/library-tests/comprehensions/ConsistencyCheck.ql @@ -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) ) diff --git a/python/ql/test/experimental/import-resolution/importflow.ql b/python/ql/test/experimental/import-resolution/importflow.ql index a3e20123fc7c4..15a4498aa1133 100644 --- a/python/ql/test/experimental/import-resolution/importflow.ql +++ b/python/ql/test/experimental/import-resolution/importflow.ql @@ -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) ) } diff --git a/python/ql/test/library-tests/ControlFlow/PointsToSupport/UseFromDefinition.ql b/python/ql/test/library-tests/ControlFlow/PointsToSupport/UseFromDefinition.ql index 8b52244478f19..6173331a2dd14 100644 --- a/python/ql/test/library-tests/ControlFlow/PointsToSupport/UseFromDefinition.ql +++ b/python/ql/test/library-tests/ControlFlow/PointsToSupport/UseFromDefinition.ql @@ -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) diff --git a/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.ql b/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.ql index c51707a65ffd2..e41f73f5b8845 100644 --- a/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.ql +++ b/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.ql @@ -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) diff --git a/python/ql/test/library-tests/PointsTo/global/Global.ql b/python/ql/test/library-tests/PointsTo/global/Global.ql index 4dc6d16d37976..9887b79fbfc7d 100644 --- a/python/ql/test/library-tests/PointsTo/global/Global.ql +++ b/python/ql/test/library-tests/PointsTo/global/Global.ql @@ -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 diff --git a/python/ql/test/library-tests/PointsTo/local/LocalPointsTo.ql b/python/ql/test/library-tests/PointsTo/local/LocalPointsTo.ql index c81bd0ed3debe..ecf67aa7b33eb 100644 --- a/python/ql/test/library-tests/PointsTo/local/LocalPointsTo.ql +++ b/python/ql/test/library-tests/PointsTo/local/LocalPointsTo.ql @@ -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 diff --git a/python/ql/test/library-tests/dataflow/tainttracking/TestTaintLib.qll b/python/ql/test/library-tests/dataflow/tainttracking/TestTaintLib.qll index 67a9f576cc752..4d7d9201e3ec2 100644 --- a/python/ql/test/library-tests/dataflow/tainttracking/TestTaintLib.qll +++ b/python/ql/test/library-tests/dataflow/tainttracking/TestTaintLib.qll @@ -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 diff --git a/python/tools/recorded-call-graph-metrics/ql/lib/RecordedCalls.qll b/python/tools/recorded-call-graph-metrics/ql/lib/RecordedCalls.qll index 55015ea5998e0..79d01776728b7 100644 --- a/python/tools/recorded-call-graph-metrics/ql/lib/RecordedCalls.qll +++ b/python/tools/recorded-call-graph-metrics/ql/lib/RecordedCalls.qll @@ -238,9 +238,10 @@ module PointsToBasedCallGraph { Value calleeValue; ResolvableRecordedCall() { - exists(Call call, XmlCallee xmlCallee | + exists(Call call, XmlCallee xmlCallee, ControlFlowNode callCfg | call = this.getACall() and - calleeValue.getACall() = call.getAFlowNode() and + callCfg.getNode() = call and + calleeValue.getACall() = callCfg and xmlCallee = this.getXmlCallee() and ( xmlCallee instanceof XmlPythonCallee and From e59907fb7a8f9dcb0848ade14f59b2d1d9062e3c Mon Sep 17 00:00:00 2001 From: yoff Date: Mon, 1 Jun 2026 13:27:08 +0000 Subject: [PATCH 3/5] Python: remove Function.getAReturnValueFlowNode() and rewrite callers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the getAFlowNode removal in the same PR: same AST→legacy-CFG bridge pattern. Rewrite the 11 call sites (across objects/, types/, frameworks/, and TypeTrackingImpl) to bind a `Return ret` explicitly, then constrain via `ret.getScope() = f and n.getNode() = ret.getValue()`. Semantic noop. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/ql/lib/semmle/python/Function.qll | 8 -------- .../python/dataflow/new/internal/TypeTrackingImpl.qll | 6 ++++-- python/ql/lib/semmle/python/frameworks/Bottle.qll | 5 ++++- python/ql/lib/semmle/python/frameworks/Django.qll | 5 ++++- python/ql/lib/semmle/python/frameworks/FastApi.qll | 5 ++++- python/ql/lib/semmle/python/frameworks/Pyramid.qll | 5 ++++- python/ql/lib/semmle/python/frameworks/Stdlib.qll | 5 +++-- python/ql/lib/semmle/python/frameworks/Twisted.qll | 5 ++++- python/ql/lib/semmle/python/objects/Callables.qll | 5 +++-- python/ql/lib/semmle/python/objects/ObjectAPI.qll | 7 ++++++- python/ql/lib/semmle/python/types/FunctionObject.qll | 5 ++++- 11 files changed, 40 insertions(+), 21 deletions(-) diff --git a/python/ql/lib/semmle/python/Function.qll b/python/ql/lib/semmle/python/Function.qll index c133275b8b78b..54f6cec2e56ba 100644 --- a/python/ql/lib/semmle/python/Function.qll +++ b/python/ql/lib/semmle/python/Function.qll @@ -153,14 +153,6 @@ class Function extends Function_, Scope, AstNode { override predicate contains(AstNode inner) { Scope.super.contains(inner) } - /** Gets a control flow node for a return value of this function */ - ControlFlowNode getAReturnValueFlowNode() { - exists(Return ret | - ret.getScope() = this and - ret.getValue() = result.getNode() - ) - } - /** Gets the minimum number of positional arguments that can be correctly passed to this function. */ int getMinPositionalArguments() { result = count(this.getAnArg()) - count(this.getDefinition().getArgs().getADefault()) diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll index 95434b05451dc..b2a5f127ea32b 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll @@ -94,8 +94,10 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input { Node returnOf(Node callable, SummaryComponent return) { return = FlowSummaryImpl::Private::SummaryComponent::return() and // `result` should be the return value of a callable expression (lambda or function) referenced by `callable` - result.asCfgNode() = - callable.getALocalSource().asExpr().(CallableExpr).getInnerScope().getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = callable.getALocalSource().asExpr().(CallableExpr).getInnerScope() and + result.asCfgNode().getNode() = ret.getValue() + ) } // Relating callables to nodes diff --git a/python/ql/lib/semmle/python/frameworks/Bottle.qll b/python/ql/lib/semmle/python/frameworks/Bottle.qll index aa2c906948dcc..d4fc54f956873 100644 --- a/python/ql/lib/semmle/python/frameworks/Bottle.qll +++ b/python/ql/lib/semmle/python/frameworks/Bottle.qll @@ -73,7 +73,10 @@ module Bottle { /** A response returned by a view callable. */ class BottleReturnResponse extends Http::Server::HttpResponse::Range { BottleReturnResponse() { - this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = any(View::ViewCallable vc) and + this.asCfgNode().getNode() = ret.getValue() + ) } override DataFlow::Node getBody() { result = this } diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index ee0ed4a84dd0c..4f3fdbff01ae2 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -2872,7 +2872,10 @@ module PrivateDjango { DataFlow::CfgNode { DjangoRedirectViewGetRedirectUrlReturn() { - node = any(GetRedirectUrlFunction f).getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = any(GetRedirectUrlFunction f) and + node.getNode() = ret.getValue() + ) } override DataFlow::Node getRedirectLocation() { result = this } diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index 9d52e9b4f57b8..db8cd8e78961c 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -309,7 +309,10 @@ module FastApi { FastApiRouteSetup routeSetup; FastApiRequestHandlerReturn() { - node = routeSetup.getARequestHandler().getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = routeSetup.getARequestHandler() and + node.getNode() = ret.getValue() + ) } override DataFlow::Node getBody() { result = this } diff --git a/python/ql/lib/semmle/python/frameworks/Pyramid.qll b/python/ql/lib/semmle/python/frameworks/Pyramid.qll index 63e19363fe860..dbfd684f02ce5 100644 --- a/python/ql/lib/semmle/python/frameworks/Pyramid.qll +++ b/python/ql/lib/semmle/python/frameworks/Pyramid.qll @@ -166,7 +166,10 @@ module Pyramid { /** A response returned by a view callable. */ private class PyramidReturnResponse extends Http::Server::HttpResponse::Range { PyramidReturnResponse() { - this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and + exists(Return ret | + ret.getScope() = any(View::ViewCallable vc) and + this.asCfgNode().getNode() = ret.getValue() + ) and not this = instance() } diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index 5d3b994880a16..41857457dd166 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -2254,8 +2254,9 @@ module StdlibPrivate { DataFlow::CfgNode { WsgirefSimpleServerApplicationReturn() { - exists(WsgirefSimpleServerApplication requestHandler | - node = requestHandler.getAReturnValueFlowNode() + exists(WsgirefSimpleServerApplication requestHandler, Return ret | + ret.getScope() = requestHandler and + node.getNode() = ret.getValue() ) } diff --git a/python/ql/lib/semmle/python/frameworks/Twisted.qll b/python/ql/lib/semmle/python/frameworks/Twisted.qll index 60aedd8fb5820..510c7738a9df4 100644 --- a/python/ql/lib/semmle/python/frameworks/Twisted.qll +++ b/python/ql/lib/semmle/python/frameworks/Twisted.qll @@ -182,7 +182,10 @@ private module Twisted { DataFlow::CfgNode { TwistedResourceRenderMethodReturn() { - this.asCfgNode() = any(TwistedResourceRenderMethod meth).getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = any(TwistedResourceRenderMethod meth) and + this.asCfgNode().getNode() = ret.getValue() + ) } override DataFlow::Node getBody() { result = this } diff --git a/python/ql/lib/semmle/python/objects/Callables.qll b/python/ql/lib/semmle/python/objects/Callables.qll index c42393cc3c627..4aba4298c941d 100644 --- a/python/ql/lib/semmle/python/objects/Callables.qll +++ b/python/ql/lib/semmle/python/objects/Callables.qll @@ -81,11 +81,12 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti pragma[nomagic] override predicate callResult(PointsToContext callee, ObjectInternal obj, CfgOrigin origin) { - exists(Function func, ControlFlowNode rval, ControlFlowNode forigin | + exists(Function func, Return ret, ControlFlowNode rval, ControlFlowNode forigin | func = this.getScope() and callee.appliesToScope(func) | - rval = func.getAReturnValueFlowNode() and + ret.getScope() = func and + rval.getNode() = ret.getValue() and PointsToInternal::pointsTo(rval, callee, obj, forigin) and origin = CfgOrigin::fromCfgNode(forigin) ) diff --git a/python/ql/lib/semmle/python/objects/ObjectAPI.qll b/python/ql/lib/semmle/python/objects/ObjectAPI.qll index a5d4d91cc7abb..d6f3b5f1fcace 100644 --- a/python/ql/lib/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/lib/semmle/python/objects/ObjectAPI.qll @@ -745,7 +745,12 @@ class PythonFunctionValue extends FunctionValue { override int maxParameters() { result = this.getScope().getMaxPositionalArguments() } /** Gets a control flow node corresponding to a return statement in this function */ - ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() } + ControlFlowNode getAReturnedNode() { + exists(Return ret | + ret.getScope() = this.getScope() and + result.getNode() = ret.getValue() + ) + } override ClassValue getARaisedType() { scope_raises(result, this.getScope()) } diff --git a/python/ql/lib/semmle/python/types/FunctionObject.qll b/python/ql/lib/semmle/python/types/FunctionObject.qll index d03f086230090..55ac1b25538d1 100644 --- a/python/ql/lib/semmle/python/types/FunctionObject.qll +++ b/python/ql/lib/semmle/python/types/FunctionObject.qll @@ -137,7 +137,10 @@ class PyFunctionObject extends FunctionObject { /** Gets a control flow node corresponding to the value of a return statement */ ControlFlowNodeWithPointsTo getAReturnedNode() { - result = this.getFunction().getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = this.getFunction() and + result.getNode() = ret.getValue() + ) } override string descriptiveString() { From 13e93e07d38d9df41f2513fc01063c63e494d0c6 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:22:18 +0000 Subject: [PATCH 4/5] Python: qualify Flow.qll's AST references with Py:: prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparatory refactor for the shared-CFG dataflow migration. Switches 'import python' to 'import python as Py' inside Flow.qll, and qualifies every AST-class reference (Expr, Bytes, Dict, AssignExpr, Compare, Module, Scope, Call, Attribute, SsaVariable, AugAssign, etc.) with the Py:: prefix. Flow.qll's own CFG types (ControlFlowNode, BasicBlock, CallNode, NameNode, DefinitionNode, CompareNode, ...) keep their unqualified names — they remain the public CFG API exported from this file. This is a semantic noop: the qualification was applied mechanically by script and no name resolution changes. Verified by: - All 361 lib/ + src/ queries compile clean. - All 186 ControlFlow + PointsTo + dataflow library-tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/ql/lib/semmle/python/Flow.qll | 368 ++++++++++++++------------- 1 file changed, 186 insertions(+), 182 deletions(-) diff --git a/python/ql/lib/semmle/python/Flow.qll b/python/ql/lib/semmle/python/Flow.qll index bdb01f4c9e2e7..0f84f3f367adf 100644 --- a/python/ql/lib/semmle/python/Flow.qll +++ b/python/ql/lib/semmle/python/Flow.qll @@ -1,7 +1,7 @@ overlay[local] module; -import python +import python as Py private import semmle.python.internal.CachedStages private import codeql.controlflow.BasicBlock as BB @@ -17,7 +17,7 @@ private import codeql.controlflow.BasicBlock as BB */ private predicate augstore(ControlFlowNode load, ControlFlowNode store) { - exists(Expr load_store | exists(AugAssign aa | aa.getTarget() = load_store) | + exists(Py::Expr load_store | exists(Py::AugAssign aa | aa.getTarget() = load_store) | toAst(load) = load_store and toAst(store) = load_store and load.strictlyDominates(store) @@ -25,7 +25,7 @@ private predicate augstore(ControlFlowNode load, ControlFlowNode store) { } /** A non-dispatched getNode() to avoid negative recursion issues */ -private AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) } +private Py::AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) } /** * A control flow node. Control flow nodes have a many-to-one relation with syntactic nodes, @@ -35,19 +35,19 @@ private AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) } class ControlFlowNode extends @py_flow_node { /** Whether this control flow node is a load (including those in augmented assignments) */ predicate isLoad() { - exists(Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this)) + exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this)) } /** Whether this control flow node is a store (including those in augmented assignments) */ predicate isStore() { - exists(Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this)) + exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this)) } /** Whether this control flow node is a delete */ - predicate isDelete() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) } + predicate isDelete() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) } /** Whether this control flow node is a parameter */ - predicate isParameter() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) } + predicate isParameter() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) } /** Whether this control flow node is a store in an augmented assignment */ predicate isAugStore() { augstore(_, this) } @@ -57,61 +57,61 @@ class ControlFlowNode extends @py_flow_node { /** Whether this flow node corresponds to a literal */ predicate isLiteral() { - toAst(this) instanceof Bytes + toAst(this) instanceof Py::Bytes or - toAst(this) instanceof Dict + toAst(this) instanceof Py::Dict or - toAst(this) instanceof DictComp + toAst(this) instanceof Py::DictComp or - toAst(this) instanceof Set + toAst(this) instanceof Py::Set or - toAst(this) instanceof SetComp + toAst(this) instanceof Py::SetComp or - toAst(this) instanceof Ellipsis + toAst(this) instanceof Py::Ellipsis or - toAst(this) instanceof GeneratorExp + toAst(this) instanceof Py::GeneratorExp or - toAst(this) instanceof Lambda + toAst(this) instanceof Py::Lambda or - toAst(this) instanceof ListComp + toAst(this) instanceof Py::ListComp or - toAst(this) instanceof List + toAst(this) instanceof Py::List or - toAst(this) instanceof Num + toAst(this) instanceof Py::Num or - toAst(this) instanceof Tuple + toAst(this) instanceof Py::Tuple or - toAst(this) instanceof Unicode + toAst(this) instanceof Py::Unicode or - toAst(this) instanceof NameConstant + toAst(this) instanceof Py::NameConstant } /** Whether this flow node corresponds to an attribute expression */ - predicate isAttribute() { toAst(this) instanceof Attribute } + predicate isAttribute() { toAst(this) instanceof Py::Attribute } /** Whether this flow node corresponds to an subscript expression */ - predicate isSubscript() { toAst(this) instanceof Subscript } + predicate isSubscript() { toAst(this) instanceof Py::Subscript } /** Whether this flow node corresponds to an import member */ - predicate isImportMember() { toAst(this) instanceof ImportMember } + predicate isImportMember() { toAst(this) instanceof Py::ImportMember } /** Whether this flow node corresponds to a call */ - predicate isCall() { toAst(this) instanceof Call } + predicate isCall() { toAst(this) instanceof Py::Call } /** Whether this flow node is the first in a module */ - predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Module } + predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Py::Module } /** Whether this flow node corresponds to an import */ - predicate isImport() { toAst(this) instanceof ImportExpr } + predicate isImport() { toAst(this) instanceof Py::ImportExpr } /** Whether this flow node corresponds to a conditional expression */ - predicate isIfExp() { toAst(this) instanceof IfExp } + predicate isIfExp() { toAst(this) instanceof Py::IfExp } /** Whether this flow node corresponds to a function definition expression */ - predicate isFunction() { toAst(this) instanceof FunctionExpr } + predicate isFunction() { toAst(this) instanceof Py::FunctionExpr } /** Whether this flow node corresponds to a class definition expression */ - predicate isClass() { toAst(this) instanceof ClassExpr } + predicate isClass() { toAst(this) instanceof Py::ClassExpr } /** Gets a predecessor of this flow node */ ControlFlowNode getAPredecessor() { this = result.getASuccessor() } @@ -123,25 +123,25 @@ class ControlFlowNode extends @py_flow_node { ControlFlowNode getImmediateDominator() { py_idoms(this, result) } /** Gets the syntactic element corresponding to this flow node */ - AstNode getNode() { py_flow_bb_node(this, result, _, _) } + Py::AstNode getNode() { py_flow_bb_node(this, result, _, _) } /** Gets a textual representation of this element. */ cached string toString() { Stages::AST::ref() and // Since modules can have ambigous names, entry nodes can too, if we do not collate them. - exists(Scope s | s.getEntryNode() = this | + exists(Py::Scope s | s.getEntryNode() = this | result = "Entry node for " + concat( | | s.toString(), ",") ) or - exists(Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString()) + exists(Py::Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString()) or - not exists(Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and + not exists(Py::Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and result = "ControlFlowNode for " + this.getNode().toString() } /** Gets the location of this ControlFlowNode */ - Location getLocation() { result = this.getNode().getLocation() } + Py::Location getLocation() { result = this.getNode().getLocation() } /** Whether this flow node is the first in its scope */ predicate isEntryNode() { py_scope_flow(this, _, -1) } @@ -151,9 +151,9 @@ class ControlFlowNode extends @py_flow_node { /** Gets the scope containing this flow node */ cached - Scope getScope() { + Py::Scope getScope() { Stages::AST::ref() and - if this.getNode() instanceof Scope + if this.getNode() instanceof Py::Scope then /* Entry or exit node */ result = this.getNode() @@ -161,7 +161,7 @@ class ControlFlowNode extends @py_flow_node { } /** Gets the enclosing module */ - Module getEnclosingModule() { result = this.getScope().getEnclosingModule() } + Py::Module getEnclosingModule() { result = this.getScope().getEnclosingModule() } /** Gets a successor for this node if the relevant condition is True. */ ControlFlowNode getATrueSuccessor() { @@ -188,7 +188,7 @@ class ControlFlowNode extends @py_flow_node { } /** Whether the scope may be exited as a result of this node raising an exception */ - predicate isExceptionalExit(Scope s) { py_scope_flow(this, s, 1) } + predicate isExceptionalExit(Py::Scope s) { py_scope_flow(this, s, 1) } /** Whether this node is a normal (non-exceptional) exit */ predicate isNormalExit() { py_scope_flow(this, _, 0) or py_scope_flow(this, _, 2) } @@ -198,7 +198,7 @@ class ControlFlowNode extends @py_flow_node { pragma[inline] predicate strictlyDominates(ControlFlowNode other) { // This predicate is gigantic, so it must be inlined. - // About 1.4 billion tuples for OpenStack Cinder. + // About 1.4 billion tuples for OpenStack Py::Cinder. this.getBasicBlock().strictlyDominates(other.getBasicBlock()) or exists(BasicBlock b, int i, int j | this = b.getNode(i) and other = b.getNode(j) and i < j) @@ -236,7 +236,7 @@ class ControlFlowNode extends @py_flow_node { /* join-ordering helper for `getAChild() */ pragma[noinline] private ControlFlowNode getExprChild(BasicBlock dom) { - this.getNode().(Expr).getAChildNode() = result.getNode() and + this.getNode().(Py::Expr).getAChildNode() = result.getNode() and result.getBasicBlock().dominates(dom) and not this instanceof UnaryExprNode } @@ -249,16 +249,16 @@ class ControlFlowNode extends @py_flow_node { */ private class AnyNode extends ControlFlowNode { - override AstNode getNode() { result = super.getNode() } + override Py::AstNode getNode() { result = super.getNode() } } /** A control flow node corresponding to a call expression, such as `func(...)` */ class CallNode extends ControlFlowNode { - CallNode() { toAst(this) instanceof Call } + CallNode() { toAst(this) instanceof Py::Call } /** Gets the flow node corresponding to the function expression for the call corresponding to this flow node */ ControlFlowNode getFunction() { - exists(Call c | + exists(Py::Call c | this.getNode() = c and c.getFunc() = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -267,7 +267,7 @@ class CallNode extends ControlFlowNode { /** Gets the flow node corresponding to the n'th positional argument of the call corresponding to this flow node */ ControlFlowNode getArg(int n) { - exists(Call c | + exists(Py::Call c | this.getNode() = c and c.getArg(n) = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -276,7 +276,7 @@ class CallNode extends ControlFlowNode { /** Gets the flow node corresponding to the named argument of the call corresponding to this flow node */ ControlFlowNode getArgByName(string name) { - exists(Call c, Keyword k | + exists(Py::Call c, Py::Keyword k | this.getNode() = c and k = c.getANamedArg() and k.getValue() = result.getNode() and @@ -292,7 +292,7 @@ class CallNode extends ControlFlowNode { result = this.getArgByName(_) } - override Call getNode() { result = super.getNode() } + override Py::Call getNode() { result = super.getNode() } predicate isDecoratorCall() { this.isClassDecoratorCall() @@ -301,11 +301,11 @@ class CallNode extends ControlFlowNode { } predicate isClassDecoratorCall() { - exists(ClassExpr cls | this.getNode() = cls.getADecoratorCall()) + exists(Py::ClassExpr cls | this.getNode() = cls.getADecoratorCall()) } predicate isFunctionDecoratorCall() { - exists(FunctionExpr func | this.getNode() = func.getADecoratorCall()) + exists(Py::FunctionExpr func | this.getNode() = func.getADecoratorCall()) } /** Gets the first tuple (*) argument of this call, if any. */ @@ -323,11 +323,11 @@ class CallNode extends ControlFlowNode { /** A control flow corresponding to an attribute expression, such as `value.attr` */ class AttrNode extends ControlFlowNode { - AttrNode() { toAst(this) instanceof Attribute } + AttrNode() { toAst(this) instanceof Py::Attribute } /** Gets the flow node corresponding to the object of the attribute expression corresponding to this flow node */ ControlFlowNode getObject() { - exists(Attribute a | + exists(Py::Attribute a | this.getNode() = a and a.getObject() = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -339,7 +339,7 @@ class AttrNode extends ControlFlowNode { * with the matching name */ ControlFlowNode getObject(string name) { - exists(Attribute a | + exists(Py::Attribute a | this.getNode() = a and a.getObject() = result.getNode() and a.getName() = name and @@ -348,57 +348,57 @@ class AttrNode extends ControlFlowNode { } /** Gets the attribute name of the attribute expression corresponding to this flow node */ - string getName() { exists(Attribute a | this.getNode() = a and a.getName() = result) } + string getName() { exists(Py::Attribute a | this.getNode() = a and a.getName() = result) } - override Attribute getNode() { result = super.getNode() } + override Py::Attribute getNode() { result = super.getNode() } } /** A control flow node corresponding to a `from ... import ...` expression */ class ImportMemberNode extends ControlFlowNode { - ImportMemberNode() { toAst(this) instanceof ImportMember } + ImportMemberNode() { toAst(this) instanceof Py::ImportMember } /** * Gets the flow node corresponding to the module in the import-member expression corresponding to this flow node, * with the matching name */ ControlFlowNode getModule(string name) { - exists(ImportMember i | this.getNode() = i and i.getModule() = result.getNode() | + exists(Py::ImportMember i | this.getNode() = i and i.getModule() = result.getNode() | i.getName() = name and result.getBasicBlock().dominates(this.getBasicBlock()) ) } - override ImportMember getNode() { result = super.getNode() } + override Py::ImportMember getNode() { result = super.getNode() } } /** A control flow node corresponding to an artificial expression representing an import */ class ImportExprNode extends ControlFlowNode { - ImportExprNode() { toAst(this) instanceof ImportExpr } + ImportExprNode() { toAst(this) instanceof Py::ImportExpr } - override ImportExpr getNode() { result = super.getNode() } + override Py::ImportExpr getNode() { result = super.getNode() } } /** A control flow node corresponding to a `from ... import *` statement */ class ImportStarNode extends ControlFlowNode { - ImportStarNode() { toAst(this) instanceof ImportStar } + ImportStarNode() { toAst(this) instanceof Py::ImportStar } /** Gets the flow node corresponding to the module in the import-star corresponding to this flow node */ ControlFlowNode getModule() { - exists(ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() | + exists(Py::ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() | result.getBasicBlock().dominates(this.getBasicBlock()) ) } - override ImportStar getNode() { result = super.getNode() } + override Py::ImportStar getNode() { result = super.getNode() } } /** A control flow node corresponding to a subscript expression, such as `value[slice]` */ class SubscriptNode extends ControlFlowNode { - SubscriptNode() { toAst(this) instanceof Subscript } + SubscriptNode() { toAst(this) instanceof Py::Subscript } /** flow node corresponding to the value of the sequence in a subscript operation */ ControlFlowNode getObject() { - exists(Subscript s | + exists(Py::Subscript s | this.getNode() = s and s.getObject() = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -407,23 +407,23 @@ class SubscriptNode extends ControlFlowNode { /** flow node corresponding to the index in a subscript operation */ ControlFlowNode getIndex() { - exists(Subscript s | + exists(Py::Subscript s | this.getNode() = s and s.getIndex() = result.getNode() and result.getBasicBlock().dominates(this.getBasicBlock()) ) } - override Subscript getNode() { result = super.getNode() } + override Py::Subscript getNode() { result = super.getNode() } } /** A control flow node corresponding to a comparison operation, such as `x DeletionNode -> NameNode('b') -> AttrNode('y') -> DeletionNode`. */ class DeletionNode extends ControlFlowNode { - DeletionNode() { toAst(this) instanceof Delete } + DeletionNode() { toAst(this) instanceof Py::Delete } /** Gets the unique target of this deletion node. */ ControlFlowNode getTarget() { result.getASuccessor() = this } @@ -617,9 +617,9 @@ class DeletionNode extends ControlFlowNode { /** A control flow node corresponding to a sequence (tuple or list) literal */ abstract class SequenceNode extends ControlFlowNode { SequenceNode() { - toAst(this) instanceof Tuple + toAst(this) instanceof Py::Tuple or - toAst(this) instanceof List + toAst(this) instanceof Py::List } /** Gets the control flow node for an element of this sequence */ @@ -632,11 +632,11 @@ abstract class SequenceNode extends ControlFlowNode { /** A control flow node corresponding to a tuple expression such as `( 1, 3, 5, 7, 9 )` */ class TupleNode extends SequenceNode { - TupleNode() { toAst(this) instanceof Tuple } + TupleNode() { toAst(this) instanceof Py::Tuple } override ControlFlowNode getElement(int n) { Stages::AST::ref() and - exists(Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and + exists(Py::Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and ( result.getBasicBlock().dominates(this.getBasicBlock()) or @@ -647,10 +647,10 @@ class TupleNode extends SequenceNode { /** A control flow node corresponding to a list expression, such as `[ 1, 3, 5, 7, 9 ]` */ class ListNode extends SequenceNode { - ListNode() { toAst(this) instanceof List } + ListNode() { toAst(this) instanceof Py::List } override ControlFlowNode getElement(int n) { - exists(List l | this.getNode() = l and result.getNode() = l.getElt(n)) and + exists(Py::List l | this.getNode() = l and result.getNode() = l.getElt(n)) and ( result.getBasicBlock().dominates(this.getBasicBlock()) or @@ -661,10 +661,10 @@ class ListNode extends SequenceNode { /** A control flow node corresponding to a set expression, such as `{ 1, 3, 5, 7, 9 }` */ class SetNode extends ControlFlowNode { - SetNode() { toAst(this) instanceof Set } + SetNode() { toAst(this) instanceof Py::Set } ControlFlowNode getAnElement() { - exists(Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and + exists(Py::Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and ( result.getBasicBlock().dominates(this.getBasicBlock()) or @@ -675,20 +675,20 @@ class SetNode extends ControlFlowNode { /** A control flow node corresponding to a dictionary literal, such as `{ 'a': 1, 'b': 2 }` */ class DictNode extends ControlFlowNode { - DictNode() { toAst(this) instanceof Dict } + DictNode() { toAst(this) instanceof Py::Dict } /** * Gets a key of this dictionary literal node, for those items that have keys * E.g, in {'a':1, **b} this returns only 'a' */ ControlFlowNode getAKey() { - exists(Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and + exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and result.getBasicBlock().dominates(this.getBasicBlock()) } /** Gets a value of this dictionary literal node */ ControlFlowNode getAValue() { - exists(Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and + exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and result.getBasicBlock().dominates(this.getBasicBlock()) } } @@ -712,21 +712,23 @@ class IterableNode extends ControlFlowNode { } } -private AstNode assigned_value(Expr lhs) { +private Py::AstNode assigned_value(Py::Expr lhs) { /* lhs = result */ - exists(Assign a | a.getATarget() = lhs and result = a.getValue()) + exists(Py::Assign a | a.getATarget() = lhs and result = a.getValue()) or /* lhs := result */ - exists(AssignExpr a | a.getTarget() = lhs and result = a.getValue()) + exists(Py::AssignExpr a | a.getTarget() = lhs and result = a.getValue()) or /* lhs : annotation = result */ - exists(AnnAssign a | a.getTarget() = lhs and result = a.getValue()) + exists(Py::AnnAssign a | a.getTarget() = lhs and result = a.getValue()) or /* import result as lhs */ - exists(Alias a | a.getAsname() = lhs and result = a.getValue()) + exists(Py::Alias a | a.getAsname() = lhs and result = a.getValue()) or /* lhs += x => result = (lhs + x) */ - exists(AugAssign a, BinaryExpr b | b = a.getOperation() and result = b and lhs = b.getLeft()) + exists(Py::AugAssign a, Py::BinaryExpr b | + b = a.getOperation() and result = b and lhs = b.getLeft() + ) or /* * ..., lhs, ... = ..., result, ... @@ -734,31 +736,31 @@ private AstNode assigned_value(Expr lhs) { * ..., (..., lhs, ...), ... = ..., (..., result, ...), ... */ - exists(Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result)) + exists(Py::Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result)) or /* for lhs in seq: => `result` is the `for` node, representing the `iter(next(seq))` operation. */ - result.(For).getTarget() = lhs + result.(Py::For).getTarget() = lhs or - exists(Parameter param | lhs = param.asName() and result = param.getDefault()) + exists(Py::Parameter param | lhs = param.asName() and result = param.getDefault()) } predicate nested_sequence_assign( - Expr left_parent, Expr right_parent, Expr left_result, Expr right_result + Py::Expr left_parent, Py::Expr right_parent, Py::Expr left_result, Py::Expr right_result ) { - exists(Assign a | + exists(Py::Assign a | a.getATarget().getASubExpression*() = left_parent and a.getValue().getASubExpression*() = right_parent ) and - exists(int i, Expr left_elem, Expr right_elem | + exists(int i, Py::Expr left_elem, Py::Expr right_elem | ( - left_elem = left_parent.(Tuple).getElt(i) + left_elem = left_parent.(Py::Tuple).getElt(i) or - left_elem = left_parent.(List).getElt(i) + left_elem = left_parent.(Py::List).getElt(i) ) and ( - right_elem = right_parent.(Tuple).getElt(i) + right_elem = right_parent.(Py::Tuple).getElt(i) or - right_elem = right_parent.(List).getElt(i) + right_elem = right_parent.(Py::List).getElt(i) ) | left_result = left_elem and right_result = right_elem @@ -769,9 +771,9 @@ predicate nested_sequence_assign( /** A flow node for a `for` statement. */ class ForNode extends ControlFlowNode { - ForNode() { toAst(this) instanceof For } + ForNode() { toAst(this) instanceof Py::For } - override For getNode() { result = super.getNode() } + override Py::For getNode() { result = super.getNode() } /** Holds if this `for` statement causes iteration over `sequence` storing each step of the iteration in `target` */ predicate iterates(ControlFlowNode target, ControlFlowNode sequence) { @@ -782,7 +784,7 @@ class ForNode extends ControlFlowNode { /** Gets the sequence node for this `for` statement. */ ControlFlowNode getSequence() { - exists(For for | + exists(Py::For for | toAst(this) = for and for.getIter() = result.getNode() | @@ -792,7 +794,7 @@ class ForNode extends ControlFlowNode { /** A possible `target` for this `for` statement, not accounting for loop unrolling */ private ControlFlowNode possibleTarget() { - exists(For for | + exists(Py::For for | toAst(this) = for and for.getTarget() = result.getNode() and this.getBasicBlock().dominates(result.getBasicBlock()) @@ -809,11 +811,11 @@ class ForNode extends ControlFlowNode { /** A flow node for a `raise` statement */ class RaiseStmtNode extends ControlFlowNode { - RaiseStmtNode() { toAst(this) instanceof Raise } + RaiseStmtNode() { toAst(this) instanceof Py::Raise } /** Gets the control flow node for the exception raised by this raise statement */ ControlFlowNode getException() { - exists(Raise r | + exists(Py::Raise r | r = toAst(this) and r.getException() = toAst(result) and result.getBasicBlock().dominates(this.getBasicBlock()) @@ -827,36 +829,36 @@ class RaiseStmtNode extends ControlFlowNode { */ class NameNode extends ControlFlowNode { NameNode() { - exists(Name n | py_flow_bb_node(this, n, _, _)) + exists(Py::Name n | py_flow_bb_node(this, n, _, _)) or - exists(PlaceHolder p | py_flow_bb_node(this, p, _, _)) + exists(Py::PlaceHolder p | py_flow_bb_node(this, p, _, _)) } /** Whether this flow node defines the variable `v`. */ - predicate defines(Variable v) { - exists(Name d | this.getNode() = d and d.defines(v)) and + predicate defines(Py::Variable v) { + exists(Py::Name d | this.getNode() = d and d.defines(v)) and not this.isLoad() } /** Whether this flow node deletes the variable `v`. */ - predicate deletes(Variable v) { exists(Name d | this.getNode() = d and d.deletes(v)) } + predicate deletes(Py::Variable v) { exists(Py::Name d | this.getNode() = d and d.deletes(v)) } /** Whether this flow node uses the variable `v`. */ - predicate uses(Variable v) { + predicate uses(Py::Variable v) { this.isLoad() and - exists(Name u | this.getNode() = u and u.uses(v)) + exists(Py::Name u | this.getNode() = u and u.uses(v)) or - exists(PlaceHolder u | - this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Load + exists(Py::PlaceHolder u | + this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Py::Load ) or Scopes::use_of_global_variable(this, v.getScope(), v.getId()) } string getId() { - result = this.getNode().(Name).getId() + result = this.getNode().(Py::Name).getId() or - result = this.getNode().(PlaceHolder).getId() + result = this.getNode().(Py::PlaceHolder).getId() } /** Whether this is a use of a local variable. */ @@ -868,37 +870,39 @@ class NameNode extends ControlFlowNode { /** Whether this is a use of a global (including builtin) variable. */ predicate isGlobal() { Scopes::use_of_global_variable(this, _, _) } - predicate isSelf() { exists(SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this) } + predicate isSelf() { + exists(Py::SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this) + } } /** A control flow node corresponding to a named constant, one of `None`, `True` or `False`. */ class NameConstantNode extends NameNode { - NameConstantNode() { exists(NameConstant n | py_flow_bb_node(this, n, _, _)) } + NameConstantNode() { exists(Py::NameConstant n | py_flow_bb_node(this, n, _, _)) } /* * We ought to override uses as well, but that has * a serious performance impact. - * deprecated predicate uses(Variable v) { none() } + * deprecated predicate uses(Py::Variable v) { none() } */ } /** A control flow node corresponding to a starred expression, `*a`. */ class StarredNode extends ControlFlowNode { - StarredNode() { toAst(this) instanceof Starred } + StarredNode() { toAst(this) instanceof Py::Starred } - ControlFlowNode getValue() { toAst(result) = toAst(this).(Starred).getValue() } + ControlFlowNode getValue() { toAst(result) = toAst(this).(Py::Starred).getValue() } } /** The ControlFlowNode for an 'except' statement. */ class ExceptFlowNode extends ControlFlowNode { - ExceptFlowNode() { this.getNode() instanceof ExceptStmt } + ExceptFlowNode() { this.getNode() instanceof Py::ExceptStmt } /** * Gets the type handled by this exception handler. - * `ExceptionType` in `except ExceptionType as e:` + * `Py::ExceptionType` in `except Py::ExceptionType as e:` */ ControlFlowNode getType() { - exists(ExceptStmt ex | + exists(Py::ExceptStmt ex | this.getBasicBlock().dominates(result.getBasicBlock()) and ex = this.getNode() and result.getNode() = ex.getType() @@ -907,10 +911,10 @@ class ExceptFlowNode extends ControlFlowNode { /** * Gets the name assigned to the handled exception, if any. - * `e` in `except ExceptionType as e:` + * `e` in `except Py::ExceptionType as e:` */ ControlFlowNode getName() { - exists(ExceptStmt ex | + exists(Py::ExceptStmt ex | this.getBasicBlock().dominates(result.getBasicBlock()) and ex = this.getNode() and result.getNode() = ex.getName() @@ -920,30 +924,30 @@ class ExceptFlowNode extends ControlFlowNode { /** The ControlFlowNode for an 'except*' statement. */ class ExceptGroupFlowNode extends ControlFlowNode { - ExceptGroupFlowNode() { this.getNode() instanceof ExceptGroupStmt } + ExceptGroupFlowNode() { this.getNode() instanceof Py::ExceptGroupStmt } /** * Gets the type handled by this exception handler. - * `ExceptionType` in `except* ExceptionType as e:` + * `Py::ExceptionType` in `except* Py::ExceptionType as e:` */ ControlFlowNode getType() { this.getBasicBlock().dominates(result.getBasicBlock()) and - result.getNode() = this.getNode().(ExceptGroupStmt).getType() + result.getNode() = this.getNode().(Py::ExceptGroupStmt).getType() } /** * Gets the name assigned to the handled exception, if any. - * `e` in `except* ExceptionType as e:` + * `e` in `except* Py::ExceptionType as e:` */ ControlFlowNode getName() { this.getBasicBlock().dominates(result.getBasicBlock()) and - result.getNode() = this.getNode().(ExceptGroupStmt).getName() + result.getNode() = this.getNode().(Py::ExceptGroupStmt).getName() } } private module Scopes { private predicate fast_local(NameNode n) { - exists(FastLocalVariable v | + exists(Py::FastLocalVariable v | n.uses(v) and v.getScope() = n.getScope() ) @@ -952,15 +956,15 @@ private module Scopes { predicate local(NameNode n) { fast_local(n) or - exists(SsaVariable var | + exists(Py::SsaVariable var | var.getAUse() = n and - n.getScope() instanceof Class and + n.getScope() instanceof Py::Class and exists(var.getDefinition()) ) } predicate non_local(NameNode n) { - exists(FastLocalVariable flv | + exists(Py::FastLocalVariable flv | flv.getALoad() = n.getNode() and not flv.getScope() = n.getScope() ) @@ -968,20 +972,20 @@ private module Scopes { // magic is fine, but we get questionable join-ordering of it pragma[nomagic] - predicate use_of_global_variable(NameNode n, Module scope, string name) { + predicate use_of_global_variable(NameNode n, Py::Module scope, string name) { n.isLoad() and not non_local(n) and - not exists(SsaVariable var | var.getAUse() = n | - var.getVariable() instanceof FastLocalVariable + not exists(Py::SsaVariable var | var.getAUse() = n | + var.getVariable() instanceof Py::FastLocalVariable or - n.getScope() instanceof Class and + n.getScope() instanceof Py::Class and not maybe_undefined(var) ) and name = n.getId() and scope = n.getEnclosingModule() } - private predicate maybe_undefined(SsaVariable var) { + private predicate maybe_undefined(Py::SsaVariable var) { not exists(var.getDefinition()) and not py_ssa_phi(var, _) or var.getDefinition().isDelete() @@ -1058,13 +1062,13 @@ class BasicBlock extends @py_flow_node { private predicate oneNodeBlock() { this.firstNode() = this.getLastNode() } private predicate startLocationInfo(string file, int line, int col) { - if this.firstNode().getNode() instanceof Scope + if this.firstNode().getNode() instanceof Py::Scope then this.firstNode().getASuccessor().getLocation().hasLocationInfo(file, line, col, _, _) else this.firstNode().getLocation().hasLocationInfo(file, line, col, _, _) } private predicate endLocationInfo(int endl, int endc) { - if this.getLastNode().getNode() instanceof Scope and not this.oneNodeBlock() + if this.getLastNode().getNode() instanceof Py::Scope and not this.oneNodeBlock() then this.getLastNode().getAPredecessor().getLocation().hasLocationInfo(_, _, _, endl, endc) else this.getLastNode().getLocation().hasLocationInfo(_, _, _, endl, endc) } @@ -1081,7 +1085,7 @@ class BasicBlock extends @py_flow_node { /** Whether flow from this basic block reaches a normal exit from its scope */ predicate reachesExit() { - exists(Scope s | s.getANormalExit().getBasicBlock() = this) + exists(Py::Scope s | s.getANormalExit().getBasicBlock() = this) or this.getASuccessor().reachesExit() } @@ -1090,7 +1094,7 @@ class BasicBlock extends @py_flow_node { * Holds if this element is at the specified location. * The location spans column `startcolumn` of line `startline` to * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see + * Py::For more information, see * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). */ predicate hasLocationInfo( @@ -1122,7 +1126,7 @@ class BasicBlock extends @py_flow_node { /** Gets the scope of this block */ pragma[nomagic] - Scope getScope() { + Py::Scope getScope() { exists(ControlFlowNode n | n.getBasicBlock() = this | /* Take care not to use an entry or exit node as that node's scope will be the outer scope */ not py_scope_flow(n, _, -1) and @@ -1145,17 +1149,17 @@ class BasicBlock extends @py_flow_node { predicate reaches(BasicBlock other) { this = other or this.strictlyReaches(other) } /** - * Gets the `ConditionBlock`, if any, that controls this block and - * does not control any other `ConditionBlock`s that control this block. - * That is the `ConditionBlock` that is closest dominator. + * Gets the `Py::ConditionBlock`, if any, that controls this block and + * does not control any other `Py::ConditionBlock`s that control this block. + * That is the `Py::ConditionBlock` that is closest dominator. */ - ConditionBlock getImmediatelyControllingBlock() { + Py::ConditionBlock getImmediatelyControllingBlock() { result = this.nonControllingImmediateDominator*().getImmediateDominator() } private BasicBlock nonControllingImmediateDominator() { result = this.getImmediateDominator() and - not result.(ConditionBlock).controls(this, _) + not result.(Py::ConditionBlock).controls(this, _) } /** @@ -1175,7 +1179,7 @@ private class ControlFlowNodeAlias = ControlFlowNode; final private class FinalBasicBlock = BasicBlock; -module Cfg implements BB::CfgSig { +module Cfg implements BB::CfgSig { private import codeql.controlflow.SuccessorType class ControlFlowNode = ControlFlowNodeAlias; @@ -1186,7 +1190,7 @@ module Cfg implements BB::CfgSig { // Using the location of the first node is simple // and we just need a way to identify the basic block // during debugging, so this will be serviceable. - Location getLocation() { result = super.getNode(0).getLocation() } + Py::Location getLocation() { result = super.getNode(0).getLocation() } int length() { result = count(int i | exists(this.getNode(i))) } From 60f7b90c159ce937e7672f13a5cba27278f96f9d Mon Sep 17 00:00:00 2001 From: yoff Date: Mon, 1 Jun 2026 13:52:30 +0200 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- python/ql/lib/semmle/python/Flow.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/lib/semmle/python/Flow.qll b/python/ql/lib/semmle/python/Flow.qll index 0f84f3f367adf..76a1f21af1572 100644 --- a/python/ql/lib/semmle/python/Flow.qll +++ b/python/ql/lib/semmle/python/Flow.qll @@ -198,7 +198,7 @@ class ControlFlowNode extends @py_flow_node { pragma[inline] predicate strictlyDominates(ControlFlowNode other) { // This predicate is gigantic, so it must be inlined. - // About 1.4 billion tuples for OpenStack Py::Cinder. + // About 1.4 billion tuples for OpenStack Cinder. this.getBasicBlock().strictlyDominates(other.getBasicBlock()) or exists(BasicBlock b, int i, int j | this = b.getNode(i) and other = b.getNode(j) and i < j) @@ -1094,7 +1094,7 @@ class BasicBlock extends @py_flow_node { * Holds if this element is at the specified location. * The location spans column `startcolumn` of line `startline` to * column `endcolumn` of line `endline` in file `filepath`. - * Py::For more information, see + * For more information, see * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). */ predicate hasLocationInfo(