From bd2004263628293a17fc1a52a368b8728b9b5d12 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 12:22:57 +0000 Subject: [PATCH] Python: wire AnnAssign into the shared CFG (green) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an `AnnAssignStmt` wrapper in `AstNodeImpl.qll` so that PEP 526 annotated assignments (`x: int = 1`, `x: int`) participate in the control flow graph. Evaluation order follows CPython: annotation, optional value, target binding. Without this, `x: int = 1` had no CFG node for `x` even though `Name.defines(v)` returns true for it on the AST side. SSA built on the new CFG would therefore miss every annotated-assignment write. Removes the corresponding MISSING: annotations from the CFG-binding gap test: - annassign.py — all four cases now green. - match_pattern.py — class-body annotated fields (`x: int`, `y: int`). - type_params.py — `item: T` inside class. Verified: all 24 ControlFlow/evaluation-order tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../controlflow/internal/AstNodeImpl.qll | 25 +++++++++++++++++++ .../ControlFlow/bindings/annassign.py | 11 ++++---- .../ControlFlow/bindings/match_pattern.py | 4 +-- .../ControlFlow/bindings/type_params.py | 2 +- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll index 681b05cdf05..d59b51375a1 100644 --- a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll +++ b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll @@ -265,6 +265,31 @@ module Ast implements AstSig { override AstNode getChild(int index) { index = 0 and result = this.getOperation() } } + /** + * An annotated assignment statement (`x: T = expr`, or `x: T` without + * value). The evaluation order follows CPython: annotation first, then + * the optional value, then the target binding. + */ + additional class AnnAssignStmt extends Stmt { + private Py::AnnAssign annAssign; + + AnnAssignStmt() { this = TPyStmt(annAssign) } + + Expr getAnnotation() { result.asExpr() = annAssign.getAnnotation() } + + Expr getValue() { result.asExpr() = annAssign.getValue() } + + Expr getTarget() { result.asExpr() = annAssign.getTarget() } + + override AstNode getChild(int index) { + index = 0 and result = this.getAnnotation() + or + index = 1 and result = this.getValue() + or + index = 2 and result = this.getTarget() + } + } + /** An assignment expression / walrus operator (`x := expr`). */ additional class NamedExpr extends Expr { private Py::AssignExpr assignExpr; diff --git a/python/ql/test/library-tests/ControlFlow/bindings/annassign.py b/python/ql/test/library-tests/ControlFlow/bindings/annassign.py index 9f80b8bffbd..7a9ae3ab6c7 100644 --- a/python/ql/test/library-tests/ControlFlow/bindings/annassign.py +++ b/python/ql/test/library-tests/ControlFlow/bindings/annassign.py @@ -1,12 +1,13 @@ # Annotated assignment (PEP 526). Both with and without an initializer. -a: int = 1 # $ MISSING: cfgdefines=a -b: str = "hi" # $ MISSING: cfgdefines=b +a: int = 1 # $ cfgdefines=a +b: str = "hi" # $ cfgdefines=b # Annotation without value: the AST records `c` as defined, -# but currently the new CFG has no node for it. -c: int # $ MISSING: cfgdefines=c +# and the new CFG now visits it via the AnnAssignStmt wrapper. +c: int # $ cfgdefines=c class K: # $ cfgdefines=K - field: int = 0 # $ MISSING: cfgdefines=field + field: int = 0 # $ cfgdefines=field + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py b/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py index 46e7a8dd4ef..66fafdcb63d 100644 --- a/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py +++ b/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py @@ -18,6 +18,6 @@ def f(subject): # $ cfgdefines=f class Point: # $ cfgdefines=Point __match_args__ = ("x", "y") # $ cfgdefines=__match_args__ - x: int # $ MISSING: cfgdefines=x - y: int # $ MISSING: cfgdefines=y + x: int # $ cfgdefines=x + y: int # $ cfgdefines=y diff --git a/python/ql/test/library-tests/ControlFlow/bindings/type_params.py b/python/ql/test/library-tests/ControlFlow/bindings/type_params.py index ab32370bd7d..a17f6e29dfd 100644 --- a/python/ql/test/library-tests/ControlFlow/bindings/type_params.py +++ b/python/ql/test/library-tests/ControlFlow/bindings/type_params.py @@ -5,7 +5,7 @@ def func[T](x: T) -> T: # $ cfgdefines=func MISSING: cfgdefines=T class Box[T]: # $ cfgdefines=Box MISSING: cfgdefines=T - item: T # $ MISSING: cfgdefines=item + item: T # $ cfgdefines=item # Multi-parameter, with bound and variadics.