From 0e1f1d9f09fb22c42e94347c810bde02bb38e019 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 21 Apr 2026 15:28:11 +0000 Subject: [PATCH] Python: Support `match` Co-authored-by: yoff --- .../controlflow/internal/AstNodeImpl.qll | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll index 7bf4f1514b0..ae2cd1d5a69 100644 --- a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll +++ b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll @@ -271,6 +271,30 @@ private module Ast { ExprNode getTarget(int n) { result.asExpr() = del.getTarget(n) } } + /** A `match` statement. */ + class MatchStmtNode extends StmtNode { + private Py::MatchStmt matchStmt; + + MatchStmtNode() { matchStmt = this.asStmt() } + + ExprNode getSubject() { result.asExpr() = matchStmt.getSubject() } + + CaseNode getCase(int n) { result.asStmt() = matchStmt.getCase(n) } + } + + /** A `case` clause in a match statement. */ + class CaseNode extends StmtNode { + private Py::Case caseStmt; + + CaseNode() { caseStmt = this.asStmt() } + + ExprNode getGuard() { result.asExpr() = caseStmt.getGuard().(Py::Guard).getTest() } + + StmtListNode getBody() { result.asStmtList() = caseStmt.getBody() } + + predicate isWildcard() { caseStmt.getPattern() instanceof Py::MatchWildcardPattern } + } + /** A `try` statement. */ class TryNode extends StmtNode { private Py::Try tryStmt; @@ -1035,31 +1059,33 @@ module AstSigImpl implements AstSig { Stmt getBody() { result = this.(Ast::ExceptionHandlerNode).getBody() } } - // ===== Switch/match — stubs for now ===== - /** A switch/match statement. Not yet implemented for Python. */ - class Switch extends AstNode { - Switch() { none() } + // ===== Switch/match ===== + /** A `match` statement, mapped to the shared CFG's `Switch`. */ + class Switch extends Stmt { + Switch() { this instanceof Ast::MatchStmtNode } - Expr getExpr() { none() } + Expr getExpr() { result = this.(Ast::MatchStmtNode).getSubject() } - Case getCase(int index) { none() } + Case getCase(int index) { result = this.(Ast::MatchStmtNode).getCase(index) } Stmt getStmt(int index) { none() } } - /** A case in a switch/match. Not yet implemented for Python. */ - class Case extends AstNode { - Case() { none() } + /** A `case` clause in a match statement. */ + class Case extends Stmt { + Case() { this instanceof Ast::CaseNode } AstNode getAPattern() { none() } - Expr getGuard() { none() } + Expr getGuard() { result = this.(Ast::CaseNode).getGuard() } - AstNode getBody() { none() } + AstNode getBody() { result = this.(Ast::CaseNode).getBody() } } - /** A default case. Not yet implemented for Python. */ - class DefaultCase extends Case { } + /** A wildcard case (`case _:`). */ + class DefaultCase extends Case { + DefaultCase() { this.(Ast::CaseNode).isWildcard() } + } // ===== Expression types ===== /** A conditional expression (`x if cond else y`). */