From 6978cecb898375214bf7f6a27ddf177d3c48890c Mon Sep 17 00:00:00 2001 From: yoff Date: Tue, 19 May 2026 12:19:28 +0000 Subject: [PATCH] Python: SSA adapter: add MultiAssignmentDefinition, definedBy, useOfDef Extends the ESSA-shaped adapter on top of the new shared SSA with the remaining APIs consumed by the dataflow library: * MultiAssignmentDefinition: matches the AST pattern 'a, b = ...' where the LHS is a Tuple/List and the Name being defined is a sub-element. Used by IterableUnpacking.qll to recognise unpacking assignments. * EssaNodeDefinition.definedBy(var, defNode): a flatter equivalent of 'getSourceVariable() = var and getDefiningNode() = defNode', matching legacy ESSA's signature. Used by DataFlowPublic.qll's ModuleVariableNode to enumerate writes of a global. * AdjacentUses::useOfDef(def, use): all reachable uses of a definition (firstUse plus transitive use-use adjacency). Used by guards in DataFlowPublic.qll. These complete the API surface enumerated by grep across the dataflow library. The remaining items (EssaNodeRefinement, EssaImportStep) are ImportResolution-specific and will need separate treatment, possibly via a different abstraction since the SSA library does not model heap-state refinements like 'foo.bar = X'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../python/dataflow/new/internal/SsaImpl.qll | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/SsaImpl.qll b/python/ql/lib/semmle/python/dataflow/new/internal/SsaImpl.qll index 4ca59ff5977..59433a96aae 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/SsaImpl.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/SsaImpl.qll @@ -238,6 +238,15 @@ class EssaNodeDefinition extends Ssa::WriteDefinition { Py::Scope getScope() { exists(Cfg::ControlFlowNode n | n = this.getDefiningNode() | result = n.getScope()) } + + /** + * Holds if this definition defines source variable `v` at CFG node + * `defNode`. Flatter form of `getSourceVariable()` + + * `getDefiningNode()`, matching legacy ESSA's `definedBy`. + */ + predicate definedBy(SsaSourceVariable v, Cfg::ControlFlowNode defNode) { + v = this.getSourceVariable() and defNode = this.getDefiningNode() + } } /** @@ -301,6 +310,23 @@ class WithDefinition extends EssaNodeDefinition { } } +/** + * An assignment where the LHS is a tuple/list and the RHS is unpacked: + * `a, b = (1, 2)` or `a, *rest = xs`. The defining node for each + * captured Name is the Name itself. + */ +class MultiAssignmentDefinition extends EssaNodeDefinition { + MultiAssignmentDefinition() { + exists(Cfg::NameNode n | n = this.getDefiningNode() | + exists(Py::Assign a, Py::Expr lhs | + a.getATarget() = lhs and + (lhs instanceof Py::Tuple or lhs instanceof Py::List) and + lhs.getASubExpression+() = n.getNode() + ) + ) + } +} + /** * An implicit entry definition for a non-local / captured / global / * builtin variable read in a scope but not defined there. @@ -384,4 +410,14 @@ module AdjacentUses { use = bb.getNode(i) ) } + + /** + * Holds if `use` is any reachable use of definition `def`. Combines + * `firstUse` with transitive use-use adjacency. + */ + predicate useOfDef(Ssa::Definition def, Cfg::NameNode use) { + firstUse(def, use) + or + exists(Cfg::NameNode mid | useOfDef(def, mid) and adjacentUseUse(mid, use)) + } }