From 58cda914db6159ec8f45ddedeaf36c020fc95db7 Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 5 May 2026 13:45:50 +0000 Subject: [PATCH] Python: collapse two-layer AstNodeImpl into a single Ast module Merge the previous `Ast` and `AstSigImpl` modules into a single `module Ast implements AstSig`. Classes now use the signature names (IfStmt, WhileStmt, ForeachStmt, etc.) and signature predicates (getCondition, getThen, getElse, etc.) directly, with no intermediate renaming layer. Drop the TStmtListNode newtype branch entirely. Replace it with a synthetic TBlockStmt(parent, slot) keyed by a parent AST node and a slot label string ('body', 'orelse', 'finally'). Py::StmtList no longer appears in the newtype; the BlockStmt class provides indexed access to the underlying body items via getStmt(n). All 22 of 24 evaluation-order tests still pass; the same 2 comprehension-related failures predate this refactor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/ql/lib/printCfgNew.ql | 2 +- .../controlflow/internal/AstNodeImpl.qll | 1976 ++++++++--------- 2 files changed, 914 insertions(+), 1064 deletions(-) diff --git a/python/ql/lib/printCfgNew.ql b/python/ql/lib/printCfgNew.ql index 7c098cbf8f6..ba336de562a 100644 --- a/python/ql/lib/printCfgNew.ql +++ b/python/ql/lib/printCfgNew.ql @@ -31,7 +31,7 @@ module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig predicate selectedSourceColumn = selectedSourceColumnAlias/0; predicate cfgScopeSpan( - AstSigImpl::Callable callable, Py::File file, int startLine, int startColumn, int endLine, + Ast::Callable callable, Py::File file, int startLine, int startColumn, int endLine, int endColumn ) { exists(Py::Scope scope | diff --git a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll index d6ac7ceb110..8a86ffa0874 100644 --- a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll +++ b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll @@ -1,11 +1,13 @@ /** - * Provides a newtype-based interface layer that mediates between the existing - * Python AST classes and the shared control-flow library's `AstSig` signature. + * Provides classes for the shared control-flow library, mediating between + * the Python AST and `AstSig`. * - * The newtype unifies Python's `Stmt`, `Expr`, `Scope`, and `StmtList` into a - * single `AstNode` type. Notably, `StmtList` (which is not an `AstNode` in the - * existing Python AST) is wrapped as a `BlockStmt` (a subtype of `Stmt`), - * since the shared CFG library expects statement blocks to be statements. + * The `Ast` module wraps Python's `Stmt`, `Expr`, `Scope`, and `Pattern`, + * and adds two synthetic kinds of node: + * - `BlockStmt`, identifying a body slot of a parent AST node (e.g. an + * `if`'s then or else branch). `Py::StmtList` itself is not directly + * wrapped. + * - Intermediate nodes for multi-operand boolean expressions. */ private import python as Py @@ -13,1019 +15,275 @@ private import codeql.controlflow.ControlFlowGraph private import codeql.controlflow.SuccessorType private import codeql.util.Void -private module Ast { - /** The newtype representing AST nodes for the shared CFG library. */ +/** Provides the Python implementation of the shared CFG `AstSig`. */ +module Ast implements AstSig { + /** + * Maps a `(parent, slot)` pair to the `Py::StmtList` that holds the items + * of the `BlockStmt` for that slot. The slot string distinguishes between + * the multiple bodies that some parents have (e.g. `if` has `body` and + * `orelse`). + */ + private Py::StmtList getBodyStmtList(Py::AstNode parent, string slot) { + result = parent.(Py::Scope).getBody() and slot = "body" + or + result = parent.(Py::If).getBody() and slot = "body" + or + result = parent.(Py::If).getOrelse() and slot = "orelse" + or + result = parent.(Py::While).getBody() and slot = "body" + or + result = parent.(Py::While).getOrelse() and slot = "orelse" + or + result = parent.(Py::For).getBody() and slot = "body" + or + result = parent.(Py::For).getOrelse() and slot = "orelse" + or + result = parent.(Py::With).getBody() and slot = "body" + or + result = parent.(Py::Try).getBody() and slot = "body" + or + result = parent.(Py::Try).getOrelse() and slot = "orelse" + or + result = parent.(Py::Try).getFinalbody() and slot = "finally" + or + result = parent.(Py::Case).getBody() and slot = "body" + or + result = parent.(Py::ExceptStmt).getBody() and slot = "body" + or + result = parent.(Py::ExceptGroupStmt).getBody() and slot = "body" + } + private newtype TAstNode = - TStmtNode(Py::Stmt s) or - TExprNode(Py::Expr e) or - TScopeNode(Py::Scope sc) or - TStmtListNode(Py::StmtList sl) or + TStmt(Py::Stmt s) or + TExpr(Py::Expr e) or + TScope(Py::Scope sc) or + TPattern(Py::Pattern p) or /** - * A synthetic node representing an intermediate pair in a multi-operand - * `and`/`or` expression. For `a and b and c` (values 0,1,2), we - * synthesize a right-nested tree: the pair at index 1 represents - * `b and c`, which becomes the right operand of the outermost pair. - * - * Only created for inner pairs (index >= 1); the outermost pair (index 0) - * is represented by the original `BoolExpr` node via `TExprNode`. + * A synthetic intermediate node in a multi-operand `and`/`or` + * expression. For `a and b and c` (operands 0, 1, 2) we model the + * operation as a right-nested tree where the inner pair at index 1 + * represents `b and c` and is the right operand of the outer pair. + * The outermost pair (index 0) is represented by the underlying + * `Py::BoolExpr` itself via `TExpr`. */ TBoolExprPair(Py::BoolExpr be, int index) { index = [1 .. count(be.getAValue()) - 2] } or - TPatternNode(Py::Pattern p) - - /** - * An AST node for the shared CFG. Each branch of the newtype gets a - * subclass that overrides `toString` and `getLocation`. - */ - class Node extends TAstNode { - string toString() { none() } - - Py::Location getLocation() { none() } - - /** Gets the enclosing scope of this node, if any. */ - ScopeNode getEnclosingScope() { none() } - } - - class StmtNode extends Node, TStmtNode { - private Py::Stmt stmt; - - StmtNode() { this = TStmtNode(stmt) } - - /** Gets the underlying Python statement. */ - Py::Stmt asStmt() { result = stmt } - - override string toString() { result = stmt.toString() } - - override Py::Location getLocation() { result = stmt.getLocation() } - - /** Gets the enclosing scope of this statement. */ - override ScopeNode getEnclosingScope() { result.asScope() = stmt.getScope() } - } - - class ExprNode extends Node, TExprNode { - private Py::Expr expr; - - ExprNode() { this = TExprNode(expr) } - - /** Gets the underlying Python expression. */ - Py::Expr asExpr() { result = expr } - - override string toString() { result = expr.toString() } - - override Py::Location getLocation() { result = expr.getLocation() } - - /** Gets the enclosing scope of this expression. */ - override ScopeNode getEnclosingScope() { result.asScope() = expr.getScope() } - } - - class ScopeNode extends Node, TScopeNode { - private Py::Scope scope; - - ScopeNode() { this = TScopeNode(scope) } - - /** Gets the underlying Python scope. */ - Py::Scope asScope() { result = scope } - - override string toString() { result = scope.toString() } - - override Py::Location getLocation() { result = scope.getLocation() } - - /** Gets the body of this scope. */ - StmtListNode getBody() { result.asStmtList() = scope.getBody() } - - /** Gets the enclosing scope of this scope, if any. */ - override ScopeNode getEnclosingScope() { result.asScope() = scope.getEnclosingScope() } - } - - class StmtListNode extends Node, TStmtListNode { - private Py::StmtList stmtList; - - StmtListNode() { this = TStmtListNode(stmtList) } - - /** Gets the underlying Python statement list. */ - Py::StmtList asStmtList() { result = stmtList } - - override string toString() { result = stmtList.toString() } - - // StmtList has no native location; approximate with first item's location. - override Py::Location getLocation() { result = stmtList.getItem(0).getLocation() } - - /** Gets the `n`th (zero-based) statement in this block. */ - StmtNode getItem(int n) { result.asStmt() = stmtList.getItem(n) } - - /** Gets the last statement in this block. */ - StmtNode getLastItem() { result.asStmt() = stmtList.getLastItem() } - - /** Gets the enclosing scope of this statement list. */ - override ScopeNode getEnclosingScope() { - result.asScope() = stmtList.getParent().(Py::Scope) - or - result.asScope() = stmtList.getParent().(Py::Stmt).getScope() - } - } - - class PatternNode extends Node, TPatternNode { - private Py::Pattern pattern; - - PatternNode() { this = TPatternNode(pattern) } - - /** Gets the underlying Python pattern. */ - Py::Pattern asPattern() { result = pattern } - - override string toString() { result = pattern.toString() } - - override Py::Location getLocation() { result = pattern.getLocation() } - - override ScopeNode getEnclosingScope() { result.asScope() = pattern.getScope() } - } - - /** An `if` statement. */ - class IfNode extends StmtNode { - private Py::If ifStmt; - - IfNode() { ifStmt = this.asStmt() } - - /** Gets the condition of this `if` statement. */ - ExprNode getTest() { result.asExpr() = ifStmt.getTest() } - - /** Gets the if-true branch. */ - StmtListNode getBody() { result.asStmtList() = ifStmt.getBody() } - - /** Gets the if-false branch, if any. */ - StmtListNode getOrelse() { result.asStmtList() = ifStmt.getOrelse() } - } - - /** An expression statement. */ - class ExprStmtNode extends StmtNode { - private Py::ExprStmt exprStmt; - - ExprStmtNode() { exprStmt = this.asStmt() } - - /** Gets the expression in this statement. */ - ExprNode getValue() { result.asExpr() = exprStmt.getValue() } - } - - /** An assignment statement (`x = y = expr`). */ - class AssignNode extends StmtNode { - private Py::Assign assign; - - AssignNode() { assign = this.asStmt() } - - ExprNode getValue() { result.asExpr() = assign.getValue() } - - ExprNode getTarget(int n) { result.asExpr() = assign.getTarget(n) } - - int getNumberOfTargets() { result = count(assign.getATarget()) } - } - - /** An augmented assignment statement (`x += expr`). */ - class AugAssignNode extends StmtNode { - private Py::AugAssign augAssign; - - AugAssignNode() { augAssign = this.asStmt() } - - ExprNode getOperation() { result.asExpr() = augAssign.getOperation() } - } - - /** An assignment expression / walrus operator (`x := expr`). */ - class AssignExprNode extends ExprNode { - private Py::AssignExpr assignExpr; - - AssignExprNode() { assignExpr = this.asExpr() } - - ExprNode getValue() { result.asExpr() = assignExpr.getValue() } - - ExprNode getTarget() { result.asExpr() = assignExpr.getTarget() } - } - - /** A `while` statement. */ - class WhileNode extends StmtNode { - private Py::While whileStmt; - - WhileNode() { whileStmt = this.asStmt() } - - ExprNode getTest() { result.asExpr() = whileStmt.getTest() } - - StmtListNode getBody() { result.asStmtList() = whileStmt.getBody() } - - StmtListNode getOrelse() { result.asStmtList() = whileStmt.getOrelse() } - } - - /** A `for` statement. */ - class ForNode extends StmtNode { - private Py::For forStmt; - - ForNode() { forStmt = this.asStmt() } - - ExprNode getTarget() { result.asExpr() = forStmt.getTarget() } - - ExprNode getIter() { result.asExpr() = forStmt.getIter() } - - StmtListNode getBody() { result.asStmtList() = forStmt.getBody() } - - StmtListNode getOrelse() { result.asStmtList() = forStmt.getOrelse() } - } - - /** A `return` statement. */ - class ReturnNode extends StmtNode { - private Py::Return ret; - - ReturnNode() { ret = this.asStmt() } - - ExprNode getValue() { result.asExpr() = ret.getValue() } - } - - /** A `raise` statement. */ - class RaiseNode extends StmtNode { - private Py::Raise raise; - - RaiseNode() { raise = this.asStmt() } - - ExprNode getException() { result.asExpr() = raise.getException() } - - ExprNode getCause() { result.asExpr() = raise.getCause() } - } - - /** A `with` statement. */ - class WithNode extends StmtNode { - private Py::With withStmt; - - WithNode() { withStmt = this.asStmt() } - - ExprNode getContextExpr() { result.asExpr() = withStmt.getContextExpr() } - - ExprNode getOptionalVars() { result.asExpr() = withStmt.getOptionalVars() } - - StmtListNode getBody() { result.asStmtList() = withStmt.getBody() } - } - - /** A `break` statement. */ - class BreakNode extends StmtNode { - BreakNode() { this.asStmt() instanceof Py::Break } - } - - /** A `continue` statement. */ - class ContinueNode extends StmtNode { - ContinueNode() { this.asStmt() instanceof Py::Continue } - } - - /** An `assert` statement. */ - class AssertNode extends StmtNode { - private Py::Assert assertStmt; - - AssertNode() { assertStmt = this.asStmt() } - - ExprNode getTest() { result.asExpr() = assertStmt.getTest() } - - ExprNode getMsg() { result.asExpr() = assertStmt.getMsg() } - } - - /** A `delete` statement. */ - class DeleteNode extends StmtNode { - private Py::Delete del; - - DeleteNode() { del = this.asStmt() } - - 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() } - - PatternNode getPattern() { result.asPattern() = caseStmt.getPattern() } - - 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; - - TryNode() { tryStmt = this.asStmt() } - - StmtListNode getBody() { result.asStmtList() = tryStmt.getBody() } - - StmtListNode getOrelse() { result.asStmtList() = tryStmt.getOrelse() } - - StmtListNode getFinalbody() { result.asStmtList() = tryStmt.getFinalbody() } - - ExceptionHandlerNode getHandler(int i) { result.asStmt() = tryStmt.getHandler(i) } - } - - /** An exception handler (`except` or `except*`). */ - class ExceptionHandlerNode extends StmtNode { - private Py::ExceptionHandler handler; - - ExceptionHandlerNode() { handler = this.asStmt() } - - ExprNode getType() { result.asExpr() = handler.getType() } - - ExprNode getName() { result.asExpr() = handler.getName() } - - StmtListNode getBody() { - result.asStmtList() = handler.(Py::ExceptStmt).getBody() or - result.asStmtList() = handler.(Py::ExceptGroupStmt).getBody() - } - } - - /** A conditional expression (`x if cond else y`). */ - class IfExpNode extends ExprNode { - private Py::IfExp ifExp; - - IfExpNode() { ifExp = this.asExpr() } - - ExprNode getTest() { result.asExpr() = ifExp.getTest() } - - ExprNode getBody() { result.asExpr() = ifExp.getBody() } - - ExprNode getOrelse() { result.asExpr() = ifExp.getOrelse() } - } - - /** A Python binary expression (arithmetic, bitwise, matmul, etc.). */ - class BinaryExprNode extends ExprNode { - private Py::BinaryExpr binExpr; - - BinaryExprNode() { binExpr = this.asExpr() } - - ExprNode getLeft() { result.asExpr() = binExpr.getLeft() } - - ExprNode getRight() { result.asExpr() = binExpr.getRight() } - } - - /** A call expression (`func(args...)`). */ - class CallNode extends ExprNode { - private Py::Call call; - - CallNode() { call = this.asExpr() } - - ExprNode getFunc() { result.asExpr() = call.getFunc() } - - ExprNode getPositionalArg(int n) { result.asExpr() = call.getPositionalArg(n) } - - int getNumberOfPositionalArgs() { result = count(call.getAPositionalArg()) } - - ExprNode getKeywordValue(int n) { - result.asExpr() = call.getNamedArg(n).(Py::Keyword).getValue() - or - result.asExpr() = call.getNamedArg(n).(Py::DictUnpacking).getValue() - } - - int getNumberOfNamedArgs() { result = count(call.getANamedArg()) } - } - - /** A subscript expression (`obj[index]`). */ - class SubscriptNode extends ExprNode { - private Py::Subscript sub; - - SubscriptNode() { sub = this.asExpr() } - - ExprNode getObject() { result.asExpr() = sub.getObject() } - - ExprNode getIndex() { result.asExpr() = sub.getIndex() } - } - - /** An attribute access (`obj.name`). */ - class AttributeNode extends ExprNode { - private Py::Attribute attr; - - AttributeNode() { attr = this.asExpr() } - - ExprNode getObject() { result.asExpr() = attr.getObject() } - } - - /** A tuple literal. */ - class TupleNode extends ExprNode { - private Py::Tuple tuple; - - TupleNode() { tuple = this.asExpr() } - - ExprNode getElt(int n) { result.asExpr() = tuple.getElt(n) } - } - - /** A list literal. */ - class ListNode extends ExprNode { - private Py::List list; - - ListNode() { list = this.asExpr() } - - ExprNode getElt(int n) { result.asExpr() = list.getElt(n) } - } - - /** A set literal. */ - class SetNode extends ExprNode { - private Py::Set set; - - SetNode() { set = this.asExpr() } - - ExprNode getElt(int n) { result.asExpr() = set.getElt(n) } - } - - /** A dict literal. */ - class DictNode extends ExprNode { - private Py::Dict dict; - - DictNode() { dict = this.asExpr() } - /** - * Gets the key of the `n`th item (at child index `2*n`), and the - * value at child index `2*n + 1`. + * A synthetic block statement, identifying one body slot of the + * `parent` AST node. The `slot` string disambiguates among multiple + * bodies of the same parent (`"body"`, `"orelse"`, `"finally"`). */ - ExprNode getKey(int n) { result.asExpr() = dict.getItem(n).(Py::KeyValuePair).getKey() } + TBlockStmt(Py::AstNode parent, string slot) { exists(getBodyStmtList(parent, slot)) } - ExprNode getValue(int n) { result.asExpr() = dict.getItem(n).(Py::KeyValuePair).getValue() } - - int getNumberOfItems() { result = count(dict.getAnItem()) } - } - - /** A unary expression other than `not` (e.g., `-x`, `+x`, `~x`). */ - class ArithmeticUnaryNode extends ExprNode { - private Py::UnaryExpr unaryExpr; - - ArithmeticUnaryNode() { unaryExpr = this.asExpr() and not unaryExpr.getOp() instanceof Py::Not } - - ExprNode getOperand() { result.asExpr() = unaryExpr.getOperand() } - } - - /** - * A comprehension or generator expression. - * The iterable is evaluated in the enclosing scope; the body runs in a - * nested synthetic function scope handled by its own CFG. - */ - class ComprehensionNode extends ExprNode { - private Py::Expr iterable; - - ComprehensionNode() { - iterable = this.asExpr().(Py::ListComp).getIterable() + /** An AST node visible to the shared CFG. */ + class AstNode extends TAstNode { + /** Gets a textual representation of this AST node. */ + string toString() { + exists(Py::Stmt s | this = TStmt(s) and result = s.toString()) or - iterable = this.asExpr().(Py::SetComp).getIterable() + exists(Py::Expr e | this = TExpr(e) and result = e.toString()) or - iterable = this.asExpr().(Py::DictComp).getIterable() + exists(Py::Scope sc | this = TScope(sc) and result = sc.toString()) or - iterable = this.asExpr().(Py::GeneratorExp).getIterable() + exists(Py::Pattern p | this = TPattern(p) and result = p.toString()) + or + exists(Py::BoolExpr be | this = TBoolExprPair(be, _) and result = be.getOperator()) + or + exists(string slot | this = TBlockStmt(_, slot) and result = "block:" + slot) } - ExprNode getIterable() { result.asExpr() = iterable } - } - - /** A comparison expression (`a < b`, `a < b < c`, etc.). */ - class CompareNode extends ExprNode { - private Py::Compare cmp; - - CompareNode() { cmp = this.asExpr() } - - ExprNode getLeft() { result.asExpr() = cmp.getLeft() } - - ExprNode getComparator(int n) { result.asExpr() = cmp.getComparator(n) } - } - - /** A slice expression (`start:stop:step`). */ - class SliceNode extends ExprNode { - private Py::Slice slice; - - SliceNode() { slice = this.asExpr() } - - ExprNode getStart() { result.asExpr() = slice.getStart() } - - ExprNode getStop() { result.asExpr() = slice.getStop() } - - ExprNode getStep() { result.asExpr() = slice.getStep() } - } - - /** A starred expression (`*x`). */ - class StarredNode extends ExprNode { - private Py::Starred starred; - - StarredNode() { starred = this.asExpr() } - - ExprNode getValue() { result.asExpr() = starred.getValue() } - } - - /** A formatted string literal (`f"...{expr}..."`). */ - class FstringNode extends ExprNode { - private Py::Fstring fstring; - - FstringNode() { fstring = this.asExpr() } - - ExprNode getValue(int n) { result.asExpr() = fstring.getValue(n) } - } - - /** A formatted value inside an f-string (`{expr}` or `{expr:spec}`). */ - class FormattedValueNode extends ExprNode { - private Py::FormattedValue fv; - - FormattedValueNode() { fv = this.asExpr() } - - ExprNode getValue() { result.asExpr() = fv.getValue() } - - ExprNode getFormatSpec() { result.asExpr() = fv.getFormatSpec() } - } - - /** A `yield` expression. */ - class YieldNode extends ExprNode { - private Py::Yield yield; - - YieldNode() { yield = this.asExpr() } - - ExprNode getValue() { result.asExpr() = yield.getValue() } - } - - /** A `yield from` expression. */ - class YieldFromNode extends ExprNode { - private Py::YieldFrom yieldFrom; - - YieldFromNode() { yieldFrom = this.asExpr() } - - ExprNode getValue() { result.asExpr() = yieldFrom.getValue() } - } - - /** An `await` expression. */ - class AwaitNode extends ExprNode { - private Py::Await await; - - AwaitNode() { await = this.asExpr() } - - ExprNode getValue() { result.asExpr() = await.getValue() } - } - - /** A class definition expression (has base classes evaluated at definition time). */ - class ClassExprNode extends ExprNode { - private Py::ClassExpr classExpr; - - ClassExprNode() { classExpr = this.asExpr() } - - ExprNode getBase(int n) { result.asExpr() = classExpr.getBase(n) } - } - - /** A function definition expression (has default args evaluated at definition time). */ - class FunctionExprNode extends ExprNode { - private Py::FunctionExpr funcExpr; - - FunctionExprNode() { funcExpr = this.asExpr() } - - ExprNode getDefault(int n) { result.asExpr() = funcExpr.getArgs().getDefault(n) } - - ExprNode getKwDefault(int n) { result.asExpr() = funcExpr.getArgs().getKwDefault(n) } - } - - /** A lambda expression (has default args evaluated at definition time). */ - class LambdaNode extends ExprNode { - private Py::Lambda lambda; - - LambdaNode() { lambda = this.asExpr() } - - ExprNode getDefault(int n) { result.asExpr() = lambda.getArgs().getDefault(n) } - - ExprNode getKwDefault(int n) { result.asExpr() = lambda.getArgs().getKwDefault(n) } - } - - /** - * A `not` expression. This is a `UnaryExpr` whose operator is `Not`. - */ - class NotExprNode extends ExprNode { - private Py::UnaryExpr notExpr; - - NotExprNode() { notExpr = this.asExpr() and notExpr.getOp() instanceof Py::Not } - - ExprNode getOperand() { result.asExpr() = notExpr.getOperand() } - } - - /** - * A boolean expression (`and`/`or`) with exactly 2 operands. - * For 2-operand BoolExprs, the `TExprNode` itself serves as the - * logical and/or expression. - */ - class BoolExpr2Node extends ExprNode { - private Py::BoolExpr boolExpr; - - BoolExpr2Node() { boolExpr = this.asExpr() and count(boolExpr.getAValue()) = 2 } - - predicate isAnd() { boolExpr.getOp() instanceof Py::And } - - predicate isOr() { boolExpr.getOp() instanceof Py::Or } - - ExprNode getLeftOperand() { result.asExpr() = boolExpr.getValue(0) } - - ExprNode getRightOperand() { result.asExpr() = boolExpr.getValue(1) } - } - - /** - * The outermost pair of a multi-operand (3+) boolean expression. - * Represented by the original `BoolExpr` node (`TExprNode`). - * Left operand is `getValue(0)`, right operand is `TBoolExprPair(be, 1)`. - */ - class BoolExprOuterNode extends ExprNode { - private Py::BoolExpr boolExpr; - - BoolExprOuterNode() { boolExpr = this.asExpr() and count(boolExpr.getAValue()) > 2 } - - predicate isAnd() { boolExpr.getOp() instanceof Py::And } - - predicate isOr() { boolExpr.getOp() instanceof Py::Or } - - Node getLeftOperand() { result = TExprNode(boolExpr.getValue(0)) } - - Node getRightOperand() { result = TBoolExprPair(boolExpr, 1) } - } - - /** - * A synthetic intermediate node in a multi-operand boolean expression. - * Pair at index `i` has left=`getValue(i)` and right=pair at `i+1` - * (or `getValue(n-1)` for the last pair). - */ - class BoolExprPairNode extends Node, TBoolExprPair { - private Py::BoolExpr boolExpr; - private int index; - - BoolExprPairNode() { this = TBoolExprPair(boolExpr, index) } - - override string toString() { result = boolExpr.getOperator() } - - override Py::Location getLocation() { result = boolExpr.getValue(index).getLocation() } - - override ScopeNode getEnclosingScope() { - result.asScope() = boolExpr.getValue(index).getScope() + /** Gets the location of this AST node. */ + Py::Location getLocation() { + exists(Py::Stmt s | this = TStmt(s) and result = s.getLocation()) + or + exists(Py::Expr e | this = TExpr(e) and result = e.getLocation()) + or + exists(Py::Scope sc | this = TScope(sc) and result = sc.getLocation()) + or + exists(Py::Pattern p | this = TPattern(p) and result = p.getLocation()) + or + exists(Py::BoolExpr be, int index | + this = TBoolExprPair(be, index) and result = be.getValue(index).getLocation() + ) + or + // BlockStmt has no native location; approximate with the first + // item's location. + exists(Py::AstNode parent, string slot | + this = TBlockStmt(parent, slot) and + result = getBodyStmtList(parent, slot).getItem(0).getLocation() + ) } - predicate isAnd() { boolExpr.getOp() instanceof Py::And } - - predicate isOr() { boolExpr.getOp() instanceof Py::Or } - - Node getLeftOperand() { result = TExprNode(boolExpr.getValue(index)) } - - Node getRightOperand() { - // Last pair: right operand is the final value - index = count(boolExpr.getAValue()) - 2 and - result = TExprNode(boolExpr.getValue(index + 1)) + /** Gets the enclosing callable that contains this node, if any. */ + Callable getEnclosingCallable() { + exists(Py::Stmt s | this = TStmt(s) and result.asScope() = s.getScope()) or - // Not last pair: right operand is the next synthetic pair - index < count(boolExpr.getAValue()) - 2 and - result = TBoolExprPair(boolExpr, index + 1) + exists(Py::Expr e | this = TExpr(e) and result.asScope() = e.getScope()) + or + exists(Py::Scope sc | this = TScope(sc) and result.asScope() = sc.getEnclosingScope()) + or + exists(Py::Pattern p | this = TPattern(p) and result.asScope() = p.getScope()) + or + exists(Py::BoolExpr be | this = TBoolExprPair(be, _) and result.asScope() = be.getScope()) + or + exists(Py::AstNode parent | this = TBlockStmt(parent, _) | + result.asScope() = parent.(Py::Scope) + or + result.asScope() = parent.(Py::Stmt).getScope() + ) } + + /** Gets the underlying Python `Stmt`, if this node wraps one. */ + Py::Stmt asStmt() { this = TStmt(result) } + + /** Gets the underlying Python `Expr`, if this node wraps one. */ + Py::Expr asExpr() { this = TExpr(result) } + + /** Gets the underlying Python `Scope`, if this node wraps one. */ + Py::Scope asScope() { this = TScope(result) } + + /** Gets the underlying Python `Pattern`, if this node wraps one. */ + Py::Pattern asPattern() { this = TPattern(result) } } - /** A `True` or `False` literal. */ - class BoolLiteralNode extends ExprNode { - BoolLiteralNode() { this.asExpr() instanceof Py::True or this.asExpr() instanceof Py::False } - - boolean getBoolValue() { - this.asExpr() instanceof Py::True and result = true - or - this.asExpr() instanceof Py::False and result = false - } - } -} - -/** Provides an implementation of the AST signature for Python. */ -module AstSigImpl implements AstSig { - class AstNode = Ast::Node; - - /** Gets the child of `n` at the specified (zero-based) index. */ - AstNode getChild(AstNode n, int index) { - // IfStmt: condition (0), then branch (1), else branch (2) - exists(Ast::IfNode ifNode | ifNode = n | - index = 0 and result = ifNode.getTest() - or - index = 1 and result = ifNode.getBody() - or - index = 2 and result = ifNode.getOrelse() - ) - or - // BlockStmt (StmtList): indexed statements - result = n.(Ast::StmtListNode).getItem(index) - or - // ExprStmt: the expression (0) - index = 0 and result = n.(Ast::ExprStmtNode).getValue() - or - // Assign: value (0), targets (1..n) - exists(Ast::AssignNode a | a = n | - index = 0 and result = a.getValue() - or - result = a.getTarget(index - 1) and index >= 1 - ) - or - // AugAssign: the operation (0) - index = 0 and result = n.(Ast::AugAssignNode).getOperation() - or - // AssignExpr (walrus :=): value (0), target (1) - exists(Ast::AssignExprNode ae | ae = n | - index = 0 and result = ae.getValue() - or - index = 1 and result = ae.getTarget() - ) - or - // WhileStmt: condition (0), body (1) - // Note: Python while/else is not directly supported by the shared library. - exists(Ast::WhileNode w | w = n | - index = 0 and result = w.getTest() - or - index = 1 and result = w.getBody() - or - index = 2 and result = w.getOrelse() - ) - or - // ForStmt (mapped as ForeachStmt): collection (0), variable (1), body (2) - exists(Ast::ForNode f | f = n | - index = 0 and result = f.getIter() - or - index = 1 and result = f.getTarget() - or - index = 2 and result = f.getBody() - ) - or - // ReturnStmt: the value (0) - index = 0 and result = n.(Ast::ReturnNode).getValue() - or - // Assert: test (0), message (1) - exists(Ast::AssertNode a | a = n | - index = 0 and result = a.getTest() - or - index = 1 and result = a.getMsg() - ) - or - // Delete: targets left to right - result = n.(Ast::DeleteNode).getTarget(index) - or - // With: context expr (0), optional vars (1), body (2) - exists(Ast::WithNode w | w = n | - index = 0 and result = w.getContextExpr() - or - index = 1 and result = w.getOptionalVars() - or - index = 2 and result = w.getBody() - ) - or - // ThrowStmt (raise): the exception (0), the cause (1) - exists(Ast::RaiseNode r | r = n | - index = 0 and result = r.getException() - or - index = 1 and result = r.getCause() - ) - or - // TryStmt: body (0), handlers (1..n), finally (-1) - exists(Ast::TryNode t | t = n | - index = 0 and result = t.getBody() - or - result = t.getHandler(index - 1) and index >= 1 - or - index = -1 and result = t.getFinalbody() - ) - or - // MatchStmt: subject (0), cases (1..n) - exists(Ast::MatchStmtNode m | m = n | - index = 0 and result = m.getSubject() - or - result = m.getCase(index - 1) and index >= 1 - ) - or - // Case: guard (0), body (1) - exists(Ast::CaseNode c | c = n | - index = 0 and result = c.getPattern() - or - index = 1 and result = c.getGuard() - or - index = 2 and result = c.getBody() - ) - or - // CatchClause (except handler): type (0), name (1), body (2) - exists(Ast::ExceptionHandlerNode h | h = n | - index = 0 and result = h.getType() - or - index = 1 and result = h.getName() - or - index = 2 and result = h.getBody() - ) - or - // ConditionalExpr (IfExp): condition (0), then (1), else (2) - exists(Ast::IfExpNode ie | ie = n | - index = 0 and result = ie.getTest() - or - index = 1 and result = ie.getBody() - or - index = 2 and result = ie.getOrelse() - ) - or - // Call: func (0), positional args (1..n), keyword values (n+1..n+k) - exists(Ast::CallNode call | call = n | - index = 0 and result = call.getFunc() - or - result = call.getPositionalArg(index - 1) and index >= 1 - or - result = call.getKeywordValue(index - 1 - call.getNumberOfPositionalArgs()) and - index >= 1 + call.getNumberOfPositionalArgs() - ) - or - // Python BinaryExpr (arithmetic, bitwise, matmul, etc.): left (0), right (1) - exists(Ast::BinaryExprNode be | be = n | - index = 0 and result = be.getLeft() - or - index = 1 and result = be.getRight() - ) - or - // Subscript (obj[index]): object (0), index (1) - exists(Ast::SubscriptNode sub | sub = n | - index = 0 and result = sub.getObject() - or - index = 1 and result = sub.getIndex() - ) - or - // Attribute (obj.name): object (0) - index = 0 and result = n.(Ast::AttributeNode).getObject() - or - // Comprehension/generator: iterable (0) - index = 0 and result = n.(Ast::ComprehensionNode).getIterable() - or - // Tuple, List, Set: elements left to right - result = n.(Ast::TupleNode).getElt(index) - or - result = n.(Ast::ListNode).getElt(index) - or - result = n.(Ast::SetNode).getElt(index) - or - // Dict: key(0), value(0), key(1), value(1), ... - exists(Ast::DictNode d, int item | d = n | - index = 2 * item and result = d.getKey(item) - or - index = 2 * item + 1 and result = d.getValue(item) - ) - or - // Arithmetic unary (-x, +x, ~x): operand (0) - index = 0 and result = n.(Ast::ArithmeticUnaryNode).getOperand() - or - // Compare (a < b < c): left (0), comparators (1..n) - exists(Ast::CompareNode cmp | cmp = n | - index = 0 and result = cmp.getLeft() - or - result = cmp.getComparator(index - 1) and index >= 1 - ) - or - // Slice (start:stop:step): start (0), stop (1), step (2) - exists(Ast::SliceNode sl | sl = n | - index = 0 and result = sl.getStart() - or - index = 1 and result = sl.getStop() - or - index = 2 and result = sl.getStep() - ) - or - // Starred (*x): value (0) - index = 0 and result = n.(Ast::StarredNode).getValue() - or - // Fstring: values left to right - result = n.(Ast::FstringNode).getValue(index) - or - // FormattedValue ({expr} or {expr:spec}): value (0), format spec (1) - exists(Ast::FormattedValueNode fv | fv = n | - index = 0 and result = fv.getValue() - or - index = 1 and result = fv.getFormatSpec() - ) - or - // Yield: value (0) - index = 0 and result = n.(Ast::YieldNode).getValue() - or - // YieldFrom: value (0) - index = 0 and result = n.(Ast::YieldFromNode).getValue() - or - // Await: value (0) - index = 0 and result = n.(Ast::AwaitNode).getValue() - or - // ClassExpr: base classes left to right - result = n.(Ast::ClassExprNode).getBase(index) - or - // FunctionExpr: defaults left to right, then kw defaults - exists(Ast::FunctionExprNode fe | fe = n | - result = fe.getDefault(index) - or - result = - fe.getKwDefault(index - - count(Py::Expr d | d = fe.asExpr().(Py::FunctionExpr).getArgs().getADefault())) - ) - or - // Lambda: defaults left to right, then kw defaults - exists(Ast::LambdaNode lam | lam = n | - result = lam.getDefault(index) - or - result = - lam.getKwDefault(index - - count(Py::Expr d | d = lam.asExpr().(Py::Lambda).getArgs().getADefault())) - ) - or - // LogicalNotExpr: operand (0) - index = 0 and result = n.(Ast::NotExprNode).getOperand() - or - // 2-operand BoolExpr: left (0), right (1) - exists(Ast::BoolExpr2Node be | be = n | - index = 0 and result = be.getLeftOperand() - or - index = 1 and result = be.getRightOperand() - ) - or - // Multi-operand BoolExpr (outermost): left (0), right (1) - exists(Ast::BoolExprOuterNode be | be = n | - index = 0 and result = be.getLeftOperand() - or - index = 1 and result = be.getRightOperand() - ) - or - // Synthetic BoolExpr pair: left (0), right (1) - exists(Ast::BoolExprPairNode bp | bp = n | - index = 0 and result = bp.getLeftOperand() - or - index = 1 and result = bp.getRightOperand() - ) - } - - Callable getEnclosingCallable(AstNode node) { result = node.getEnclosingScope() } + /** Gets the immediately enclosing callable that contains `node`. */ + Callable getEnclosingCallable(AstNode node) { result = node.getEnclosingCallable() } /** * A callable: a function, class, or module scope. * * In Python, all three are executable scopes with statement bodies. */ - class Callable extends Ast::ScopeNode { } + class Callable extends AstNode, TScope { } /** Gets the body of callable `c`. */ - AstNode callableGetBody(Callable c) { result = c.getBody() } + AstNode callableGetBody(Callable c) { result = TBlockStmt(c.asScope(), "body") } - /** A statement. Includes both wrapped `Stmt` nodes and `StmtList` blocks. */ + /** A statement. */ class Stmt extends AstNode { - Stmt() { this instanceof Ast::StmtNode or this instanceof Ast::StmtListNode } + Stmt() { this instanceof TStmt or this instanceof TBlockStmt } } - /** An expression. Includes `TExprNode` and synthetic `TBoolExprPair` nodes. */ + /** An expression. */ class Expr extends AstNode { - Expr() { this instanceof Ast::ExprNode or this instanceof Ast::BoolExprPairNode } + Expr() { this instanceof TExpr or this instanceof TBoolExprPair } } - /** A block of statements, wrapping Python's `StmtList`. */ - class BlockStmt extends Stmt, Ast::StmtListNode { + /** A pattern in a `match` statement. */ + additional class Pattern extends AstNode, TPattern { } + + /** + * A block statement, modeling the body of a parent AST node as a + * sequence of statements. + */ + class BlockStmt extends Stmt, TBlockStmt { + private Py::AstNode parent; + private string slot; + + BlockStmt() { this = TBlockStmt(parent, slot) } + /** Gets the `n`th (zero-based) statement in this block. */ - Stmt getStmt(int n) { result = Ast::StmtListNode.super.getItem(n) } + Stmt getStmt(int n) { result = TStmt(getBodyStmtList(parent, slot).getItem(n)) } /** Gets the last statement in this block. */ - Stmt getLastStmt() { result = Ast::StmtListNode.super.getLastItem() } + Stmt getLastStmt() { result = TStmt(getBodyStmtList(parent, slot).getLastItem()) } } /** An expression statement. */ - class ExprStmt extends Stmt, Ast::ExprStmtNode { + class ExprStmt extends Stmt { + private Py::ExprStmt exprStmt; + + ExprStmt() { exprStmt = this.asStmt() } + /** Gets the expression in this expression statement. */ - Expr getExpr() { result = this.getValue() } + Expr getExpr() { result = TExpr(exprStmt.getValue()) } + } + + /** An assignment statement (`x = y = expr`). */ + additional class AssignStmt extends Stmt { + private Py::Assign assign; + + AssignStmt() { assign = this.asStmt() } + + Expr getValue() { result = TExpr(assign.getValue()) } + + Expr getTarget(int n) { result = TExpr(assign.getTarget(n)) } + + int getNumberOfTargets() { result = count(assign.getATarget()) } + } + + /** An augmented assignment statement (`x += expr`). */ + additional class AugAssignStmt extends Stmt { + private Py::AugAssign augAssign; + + AugAssignStmt() { augAssign = this.asStmt() } + + Expr getOperation() { result = TExpr(augAssign.getOperation()) } + } + + /** An assignment expression / walrus operator (`x := expr`). */ + additional class NamedExpr extends Expr { + private Py::AssignExpr assignExpr; + + NamedExpr() { assignExpr = this.asExpr() } + + Expr getValue() { result = TExpr(assignExpr.getValue()) } + + Expr getTarget() { result = TExpr(assignExpr.getTarget()) } } /** * An `if` statement. * * Python's `elif` chains are represented as nested `If` nodes in the - * else branch's `StmtList`. The shared CFG library handles this naturally: - * `getElse()` returns the `BlockStmt` wrapping the else branch, and if that - * block contains a single `If`, the result is a chained conditional. + * else branch's `StmtList`. The shared CFG library handles this + * naturally: `getElse()` returns the `BlockStmt` wrapping the else + * branch, and if that block contains a single `If`, the result is + * a chained conditional. */ - class IfStmt extends Stmt, Ast::IfNode { + class IfStmt extends Stmt { + private Py::If ifStmt; + + IfStmt() { ifStmt = this.asStmt() } + + /** Gets the underlying Python `If` statement. */ + Py::If asIf() { result = ifStmt } + /** Gets the condition of this `if` statement. */ - Expr getCondition() { result = this.getTest() } + Expr getCondition() { result = TExpr(ifStmt.getTest()) } /** Gets the `then` (true) branch of this `if` statement. */ - Stmt getThen() { result = Ast::IfNode.super.getBody() } + Stmt getThen() { result = TBlockStmt(ifStmt, "body") } - /** Gets the `else` (false) branch of this `if` statement, if any. */ - Stmt getElse() { result = this.getOrelse() } + /** Gets the `else` (false) branch, if any. */ + Stmt getElse() { result = TBlockStmt(ifStmt, "orelse") } } - // ===== Loop statements ===== /** A loop statement. */ class LoopStmt extends Stmt { - LoopStmt() { this instanceof Ast::WhileNode or this instanceof Ast::ForNode } + LoopStmt() { this.asStmt() instanceof Py::While or this.asStmt() instanceof Py::For } /** Gets the body of this loop statement. */ Stmt getBody() { none() } } /** A `while` loop statement. */ - class WhileStmt extends LoopStmt instanceof Ast::WhileNode { - /** Gets the boolean condition of this `while` loop. */ - Expr getCondition() { result = this.(Ast::WhileNode).getTest() } + class WhileStmt extends LoopStmt { + private Py::While whileStmt; - override Stmt getBody() { result = this.(Ast::WhileNode).getBody() } + WhileStmt() { whileStmt = this.asStmt() } + + /** Gets the boolean condition of this `while` loop. */ + Expr getCondition() { result = TExpr(whileStmt.getTest()) } + + override Stmt getBody() { result = TBlockStmt(whileStmt, "body") } + + /** Gets the `else` branch of this `while` loop, if any. */ + Stmt getElse() { result = TBlockStmt(whileStmt, "orelse") } } - /** A `do-while` loop statement. Python has no do-while construct. */ + /** + * A `do-while` loop statement. Python has no do-while construct. + */ class DoStmt extends LoopStmt { DoStmt() { none() } @@ -1045,34 +303,30 @@ module AstSigImpl implements AstSig { /** A for-each loop (`for x in iterable:`). */ class ForeachStmt extends LoopStmt { - ForeachStmt() { this instanceof Ast::ForNode } + private Py::For forStmt; + + ForeachStmt() { forStmt = this.asStmt() } /** Gets the loop variable. */ - Expr getVariable() { result = this.(Ast::ForNode).getTarget() } + Expr getVariable() { result = TExpr(forStmt.getTarget()) } /** Gets the collection being iterated. */ - Expr getCollection() { result = this.(Ast::ForNode).getIter() } + Expr getCollection() { result = TExpr(forStmt.getIter()) } - override Stmt getBody() { result = this.(Ast::ForNode).getBody() } + override Stmt getBody() { result = TBlockStmt(forStmt, "body") } + + /** Gets the `else` branch of this `for` loop, if any. */ + Stmt getElse() { result = TBlockStmt(forStmt, "orelse") } } - // ===== Abrupt completion statements ===== /** A `break` statement. */ - class BreakStmt extends Stmt, Ast::BreakNode { } + class BreakStmt extends Stmt { + BreakStmt() { this.asStmt() instanceof Py::Break } + } /** A `continue` statement. */ - class ContinueStmt extends Stmt, Ast::ContinueNode { } - - /** A `return` statement. */ - class ReturnStmt extends Stmt, Ast::ReturnNode { - /** Gets the expression being returned, if any. */ - Expr getExpr() { result = this.getValue() } - } - - /** A `raise` statement (mapped to `Throw`). */ - class Throw extends Stmt, Ast::RaiseNode { - /** Gets the expression being raised. */ - Expr getExpr() { result = this.getException() } + class ContinueStmt extends Stmt { + ContinueStmt() { this.asStmt() instanceof Py::Continue } } /** A `goto` statement. Python has no goto. */ @@ -1080,70 +334,154 @@ module AstSigImpl implements AstSig { GotoStmt() { none() } } - // ===== Try/except ===== + /** A `return` statement. */ + class ReturnStmt extends Stmt { + private Py::Return ret; + + ReturnStmt() { ret = this.asStmt() } + + /** Gets the expression being returned, if any. */ + Expr getExpr() { result = TExpr(ret.getValue()) } + } + + /** A `raise` statement (mapped to `Throw`). */ + class Throw extends Stmt { + private Py::Raise raise; + + Throw() { raise = this.asStmt() } + + /** Gets the expression being raised. */ + Expr getExpr() { result = TExpr(raise.getException()) } + + /** Gets the cause of this `raise`, if any. */ + Expr getCause() { result = TExpr(raise.getCause()) } + } + + /** A `with` statement. */ + additional class WithStmt extends Stmt { + private Py::With withStmt; + + WithStmt() { withStmt = this.asStmt() } + + Expr getContextExpr() { result = TExpr(withStmt.getContextExpr()) } + + Expr getOptionalVars() { result = TExpr(withStmt.getOptionalVars()) } + + Stmt getBody() { result = TBlockStmt(withStmt, "body") } + } + + /** An `assert` statement. */ + additional class AssertStmt extends Stmt { + private Py::Assert assertStmt; + + AssertStmt() { assertStmt = this.asStmt() } + + Expr getTest() { result = TExpr(assertStmt.getTest()) } + + Expr getMsg() { result = TExpr(assertStmt.getMsg()) } + } + + /** A `delete` statement. */ + additional class DeleteStmt extends Stmt { + private Py::Delete del; + + DeleteStmt() { del = this.asStmt() } + + Expr getTarget(int n) { result = TExpr(del.getTarget(n)) } + } + /** A `try` statement. */ class TryStmt extends Stmt { - TryStmt() { this instanceof Ast::TryNode } + private Py::Try tryStmt; - Stmt getBody() { result = this.(Ast::TryNode).getBody() } + TryStmt() { tryStmt = this.asStmt() } - CatchClause getCatch(int index) { result = this.(Ast::TryNode).getHandler(index) } + Stmt getBody() { result = TBlockStmt(tryStmt, "body") } - Stmt getFinally() { result = this.(Ast::TryNode).getFinalbody() } + /** Gets the `else` branch of this `try` statement, if any. */ + Stmt getElse() { result = TBlockStmt(tryStmt, "orelse") } + + Stmt getFinally() { result = TBlockStmt(tryStmt, "finally") } + + CatchClause getCatch(int index) { result = TStmt(tryStmt.getHandler(index)) } } - AstNode getTryElse(TryStmt try) { result = try.(Ast::TryNode).getOrelse() } + /** + * Gets the `else` branch of `try` statement `try`, if any. + */ + AstNode getTryElse(TryStmt try) { result = try.getElse() } - /** An except clause in a try statement. */ + /** An exception handler (`except` or `except*`). */ class CatchClause extends Stmt { - CatchClause() { this instanceof Ast::ExceptionHandlerNode } + private Py::ExceptionHandler handler; - AstNode getVariable() { result = this.(Ast::ExceptionHandlerNode).getName() } + CatchClause() { handler = this.asStmt() } + /** Gets the type expression of this exception handler. */ + Expr getType() { result = TExpr(handler.getType()) } + + /** Gets the variable name of this exception handler, if any. */ + AstNode getVariable() { result = TExpr(handler.getName()) } + + /** Holds: catch clauses do not have a `Condition` in Python's model. */ Expr getCondition() { none() } - Stmt getBody() { result = this.(Ast::ExceptionHandlerNode).getBody() } + /** Gets the body of this exception handler. */ + Stmt getBody() { + result = TBlockStmt(handler.(Py::ExceptStmt), "body") + or + result = TBlockStmt(handler.(Py::ExceptGroupStmt), "body") + } } - // ===== Switch/match ===== /** A `match` statement, mapped to the shared CFG's `Switch`. */ class Switch extends Stmt { - Switch() { this instanceof Ast::MatchStmtNode } + private Py::MatchStmt matchStmt; - Expr getExpr() { result = this.(Ast::MatchStmtNode).getSubject() } + Switch() { matchStmt = this.asStmt() } - Case getCase(int index) { result = this.(Ast::MatchStmtNode).getCase(index) } + Expr getExpr() { result = TExpr(matchStmt.getSubject()) } + + Case getCase(int index) { result = TStmt(matchStmt.getCase(index)) } Stmt getStmt(int index) { none() } } /** A `case` clause in a match statement. */ class Case extends Stmt { - Case() { this instanceof Ast::CaseNode } + private Py::Case caseStmt; - AstNode getAPattern() { result = this.(Ast::CaseNode).getPattern() } + Case() { caseStmt = this.asStmt() } - Expr getGuard() { result = this.(Ast::CaseNode).getGuard() } + AstNode getAPattern() { result = TPattern(caseStmt.getPattern()) } - AstNode getBody() { result = this.(Ast::CaseNode).getBody() } + Expr getGuard() { result = TExpr(caseStmt.getGuard().(Py::Guard).getTest()) } + + AstNode getBody() { result = TBlockStmt(caseStmt, "body") } + + /** Holds if this case is a wildcard pattern (`case _:`). */ + predicate isWildcard() { caseStmt.getPattern() instanceof Py::MatchWildcardPattern } } /** A wildcard case (`case _:`). */ class DefaultCase extends Case { - DefaultCase() { this.(Ast::CaseNode).isWildcard() } + DefaultCase() { this.isWildcard() } } - // ===== Expression types ===== /** A conditional expression (`x if cond else y`). */ - class ConditionalExpr extends Expr, Ast::IfExpNode { + class ConditionalExpr extends Expr { + private Py::IfExp ifExp; + + ConditionalExpr() { ifExp = this.asExpr() } + /** Gets the condition of this expression. */ - Expr getCondition() { result = this.getTest() } + Expr getCondition() { result = TExpr(ifExp.getTest()) } /** Gets the true branch of this expression. */ - Expr getThen() { result = Ast::IfExpNode.super.getBody() } + Expr getThen() { result = TExpr(ifExp.getBody()) } /** Gets the false branch of this expression. */ - Expr getElse() { result = this.getOrelse() } + Expr getElse() { result = TExpr(ifExp.getOrelse()) } } /** @@ -1152,45 +490,72 @@ module AstSigImpl implements AstSig { */ class BinaryExpr extends Expr { BinaryExpr() { - this instanceof Ast::BoolExpr2Node or - this instanceof Ast::BoolExprOuterNode or - this instanceof Ast::BoolExprPairNode + exists(Py::BoolExpr be | this = TExpr(be) and count(be.getAValue()) >= 2) + or + this instanceof TBoolExprPair } - /** Gets the left operand. */ + /** Gets the left operand of this binary expression. */ Expr getLeftOperand() { - result = this.(Ast::BoolExpr2Node).getLeftOperand() + exists(Py::BoolExpr be | this = TExpr(be) and result = TExpr(be.getValue(0))) or - result = this.(Ast::BoolExprOuterNode).getLeftOperand() - or - result = this.(Ast::BoolExprPairNode).getLeftOperand() + exists(Py::BoolExpr be, int i | + this = TBoolExprPair(be, i) and result = TExpr(be.getValue(i)) + ) } - /** Gets the right operand. */ + /** Gets the right operand of this binary expression. */ Expr getRightOperand() { - result = this.(Ast::BoolExpr2Node).getRightOperand() + // 2-operand BoolExpr: right operand is value(1). + exists(Py::BoolExpr be | + this = TExpr(be) and + count(be.getAValue()) = 2 and + result = TExpr(be.getValue(1)) + ) or - result = this.(Ast::BoolExprOuterNode).getRightOperand() + // 3+ operand BoolExpr (outermost): right operand is the synthetic + // pair at index 1. + exists(Py::BoolExpr be | + this = TExpr(be) and + count(be.getAValue()) > 2 and + result = TBoolExprPair(be, 1) + ) or - result = this.(Ast::BoolExprPairNode).getRightOperand() + // Last synthetic pair: right operand is the final value. + exists(Py::BoolExpr be, int i, int n | + this = TBoolExprPair(be, i) and + n = count(be.getAValue()) and + i = n - 2 and + result = TExpr(be.getValue(i + 1)) + ) + or + // Non-last synthetic pair: right operand is the next pair. + exists(Py::BoolExpr be, int i, int n | + this = TBoolExprPair(be, i) and + n = count(be.getAValue()) and + i < n - 2 and + result = TBoolExprPair(be, i + 1) + ) } } /** A short-circuiting logical `and` expression. */ class LogicalAndExpr extends BinaryExpr { LogicalAndExpr() { - this.(Ast::BoolExpr2Node).isAnd() or - this.(Ast::BoolExprOuterNode).isAnd() or - this.(Ast::BoolExprPairNode).isAnd() + exists(Py::BoolExpr be | + be.getOp() instanceof Py::And and + (this = TExpr(be) or this = TBoolExprPair(be, _)) + ) } } /** A short-circuiting logical `or` expression. */ class LogicalOrExpr extends BinaryExpr { LogicalOrExpr() { - this.(Ast::BoolExpr2Node).isOr() or - this.(Ast::BoolExprOuterNode).isOr() or - this.(Ast::BoolExprPairNode).isOr() + exists(Py::BoolExpr be | + be.getOp() instanceof Py::Or and + (this = TExpr(be) or this = TBoolExprPair(be, _)) + ) } } @@ -1199,43 +564,47 @@ module AstSigImpl implements AstSig { NullCoalescingExpr() { none() } } - /** An assignment expression. Python has no assignment expressions in the BinaryExpr sense. */ - class Assignment extends BinaryExpr { - Assignment() { none() } - } - - /** A simple assignment expression. */ - class AssignExpr extends Assignment { } - - /** A compound assignment expression. */ - class CompoundAssignment extends Assignment { } - - /** A short-circuiting logical AND compound assignment. Python has no `&&=` operator. */ - class AssignLogicalAndExpr extends CompoundAssignment { } - - /** A short-circuiting logical OR compound assignment. Python has no `||=` operator. */ - class AssignLogicalOrExpr extends CompoundAssignment { } - - /** A short-circuiting null-coalescing compound assignment. Python has no `??=` operator. */ - class AssignNullCoalescingExpr extends CompoundAssignment { } - - /** A unary expression. Exists for the `not` subclass. */ + /** + * A unary expression. Currently only used for the `not` subclass. + */ class UnaryExpr extends Expr { - UnaryExpr() { this instanceof Ast::NotExprNode } + UnaryExpr() { this.asExpr().(Py::UnaryExpr).getOp() instanceof Py::Not } - Expr getOperand() { result = this.(Ast::NotExprNode).getOperand() } + /** Gets the operand of this unary expression. */ + Expr getOperand() { result = TExpr(this.asExpr().(Py::UnaryExpr).getOperand()) } } /** A logical `not` expression. */ class LogicalNotExpr extends UnaryExpr { } - /** A boolean literal expression (`True` or `False`). */ - class BooleanLiteral extends Expr, Ast::BoolLiteralNode { - /** Gets the boolean value of this literal. */ - boolean getValue() { result = this.getBoolValue() } + /** An assignment expression. Python's walrus is modelled separately. */ + class Assignment extends BinaryExpr { + Assignment() { none() } } - /** A pattern match expression. Python has no `instanceof`-style pattern match expr. */ + class AssignExpr extends Assignment { } + + class CompoundAssignment extends Assignment { } + + class AssignLogicalAndExpr extends CompoundAssignment { } + + class AssignLogicalOrExpr extends CompoundAssignment { } + + class AssignNullCoalescingExpr extends CompoundAssignment { } + + /** A boolean literal expression (`True` or `False`). */ + class BooleanLiteral extends Expr { + BooleanLiteral() { this.asExpr() instanceof Py::True or this.asExpr() instanceof Py::False } + + /** Gets the boolean value of this literal. */ + boolean getValue() { + this.asExpr() instanceof Py::True and result = true + or + this.asExpr() instanceof Py::False and result = false + } + } + + /** A pattern match expression. Python has no `instanceof`-style pattern match expression. */ class PatternMatchExpr extends Expr { PatternMatchExpr() { none() } @@ -1243,9 +612,490 @@ module AstSigImpl implements AstSig { AstNode getPattern() { none() } } + + // ===== Python-specific expression classes (used by `getChild`) ===== + /** A Python binary expression (arithmetic, bitwise, matmul, etc.). */ + additional class ArithBinaryExpr extends Expr { + private Py::BinaryExpr binExpr; + + ArithBinaryExpr() { binExpr = this.asExpr() } + + Expr getLeft() { result = TExpr(binExpr.getLeft()) } + + Expr getRight() { result = TExpr(binExpr.getRight()) } + } + + /** A call expression (`func(args...)`). */ + additional class CallExpr extends Expr { + private Py::Call call; + + CallExpr() { call = this.asExpr() } + + Expr getFunc() { result = TExpr(call.getFunc()) } + + Expr getPositionalArg(int n) { result = TExpr(call.getPositionalArg(n)) } + + int getNumberOfPositionalArgs() { result = count(call.getAPositionalArg()) } + + Expr getKeywordValue(int n) { + result = TExpr(call.getNamedArg(n).(Py::Keyword).getValue()) + or + result = TExpr(call.getNamedArg(n).(Py::DictUnpacking).getValue()) + } + + int getNumberOfNamedArgs() { result = count(call.getANamedArg()) } + } + + /** A subscript expression (`obj[index]`). */ + additional class SubscriptExpr extends Expr { + private Py::Subscript sub; + + SubscriptExpr() { sub = this.asExpr() } + + Expr getObject() { result = TExpr(sub.getObject()) } + + Expr getIndex() { result = TExpr(sub.getIndex()) } + } + + /** An attribute access (`obj.name`). */ + additional class AttributeExpr extends Expr { + private Py::Attribute attr; + + AttributeExpr() { attr = this.asExpr() } + + Expr getObject() { result = TExpr(attr.getObject()) } + } + + /** A tuple literal. */ + additional class TupleExpr extends Expr { + private Py::Tuple tuple; + + TupleExpr() { tuple = this.asExpr() } + + Expr getElt(int n) { result = TExpr(tuple.getElt(n)) } + } + + /** A list literal. */ + additional class ListExpr extends Expr { + private Py::List list; + + ListExpr() { list = this.asExpr() } + + Expr getElt(int n) { result = TExpr(list.getElt(n)) } + } + + /** A set literal. */ + additional class SetExpr extends Expr { + private Py::Set set; + + SetExpr() { set = this.asExpr() } + + Expr getElt(int n) { result = TExpr(set.getElt(n)) } + } + + /** A dict literal. */ + additional class DictExpr extends Expr { + private Py::Dict dict; + + DictExpr() { dict = this.asExpr() } + + /** + * Gets the key of the `n`th item (at child index `2*n`); the value is + * at child index `2*n + 1`. + */ + Expr getKey(int n) { result = TExpr(dict.getItem(n).(Py::KeyValuePair).getKey()) } + + Expr getValue(int n) { result = TExpr(dict.getItem(n).(Py::KeyValuePair).getValue()) } + + int getNumberOfItems() { result = count(dict.getAnItem()) } + } + + /** A unary expression other than `not` (e.g., `-x`, `+x`, `~x`). */ + additional class ArithUnaryExpr extends Expr { + private Py::UnaryExpr unaryExpr; + + ArithUnaryExpr() { unaryExpr = this.asExpr() and not unaryExpr.getOp() instanceof Py::Not } + + Expr getOperand() { result = TExpr(unaryExpr.getOperand()) } + } + + /** + * A comprehension or generator expression. The iterable is evaluated in + * the enclosing scope; the body runs in a nested synthetic function + * scope handled by its own CFG. + */ + additional class Comprehension extends Expr { + private Py::Expr iterable; + + Comprehension() { + iterable = this.asExpr().(Py::ListComp).getIterable() + or + iterable = this.asExpr().(Py::SetComp).getIterable() + or + iterable = this.asExpr().(Py::DictComp).getIterable() + or + iterable = this.asExpr().(Py::GeneratorExp).getIterable() + } + + Expr getIterable() { result = TExpr(iterable) } + } + + /** A comparison expression (`a < b`, `a < b < c`, etc.). */ + additional class CompareExpr extends Expr { + private Py::Compare cmp; + + CompareExpr() { cmp = this.asExpr() } + + Expr getLeft() { result = TExpr(cmp.getLeft()) } + + Expr getComparator(int n) { result = TExpr(cmp.getComparator(n)) } + } + + /** A slice expression (`start:stop:step`). */ + additional class SliceExpr extends Expr { + private Py::Slice slice; + + SliceExpr() { slice = this.asExpr() } + + Expr getStart() { result = TExpr(slice.getStart()) } + + Expr getStop() { result = TExpr(slice.getStop()) } + + Expr getStep() { result = TExpr(slice.getStep()) } + } + + /** A starred expression (`*x`). */ + additional class StarredExpr extends Expr { + private Py::Starred starred; + + StarredExpr() { starred = this.asExpr() } + + Expr getValue() { result = TExpr(starred.getValue()) } + } + + /** A formatted string literal (`f"...{expr}..."`). */ + additional class FstringExpr extends Expr { + private Py::Fstring fstring; + + FstringExpr() { fstring = this.asExpr() } + + Expr getValue(int n) { result = TExpr(fstring.getValue(n)) } + } + + /** A formatted value inside an f-string (`{expr}` or `{expr:spec}`). */ + additional class FormattedValueExpr extends Expr { + private Py::FormattedValue fv; + + FormattedValueExpr() { fv = this.asExpr() } + + Expr getValue() { result = TExpr(fv.getValue()) } + + Expr getFormatSpec() { result = TExpr(fv.getFormatSpec()) } + } + + /** A `yield` expression. */ + additional class YieldExpr extends Expr { + private Py::Yield yield; + + YieldExpr() { yield = this.asExpr() } + + Expr getValue() { result = TExpr(yield.getValue()) } + } + + /** A `yield from` expression. */ + additional class YieldFromExpr extends Expr { + private Py::YieldFrom yieldFrom; + + YieldFromExpr() { yieldFrom = this.asExpr() } + + Expr getValue() { result = TExpr(yieldFrom.getValue()) } + } + + /** An `await` expression. */ + additional class AwaitExpr extends Expr { + private Py::Await await; + + AwaitExpr() { await = this.asExpr() } + + Expr getValue() { result = TExpr(await.getValue()) } + } + + /** A class definition expression (has base classes evaluated at definition time). */ + additional class ClassDefExpr extends Expr { + private Py::ClassExpr classExpr; + + ClassDefExpr() { classExpr = this.asExpr() } + + Expr getBase(int n) { result = TExpr(classExpr.getBase(n)) } + } + + /** A function definition expression (has default args evaluated at definition time). */ + additional class FunctionDefExpr extends Expr { + private Py::FunctionExpr funcExpr; + + FunctionDefExpr() { funcExpr = this.asExpr() } + + Expr getDefault(int n) { result = TExpr(funcExpr.getArgs().getDefault(n)) } + + Expr getKwDefault(int n) { result = TExpr(funcExpr.getArgs().getKwDefault(n)) } + + int getNumberOfDefaults() { result = count(funcExpr.getArgs().getADefault()) } + } + + /** A lambda expression (has default args evaluated at definition time). */ + additional class LambdaExpr extends Expr { + private Py::Lambda lambda; + + LambdaExpr() { lambda = this.asExpr() } + + Expr getDefault(int n) { result = TExpr(lambda.getArgs().getDefault(n)) } + + Expr getKwDefault(int n) { result = TExpr(lambda.getArgs().getKwDefault(n)) } + + int getNumberOfDefaults() { result = count(lambda.getArgs().getADefault()) } + } + + /** Gets the child of `n` at the specified (zero-based) index. */ + AstNode getChild(AstNode n, int index) { + // BlockStmt: indexed statements + result = n.(BlockStmt).getStmt(index) + or + // IfStmt: condition (0), then (1), else (2) + exists(IfStmt ifStmt | ifStmt = n | + index = 0 and result = ifStmt.getCondition() + or + index = 1 and result = ifStmt.getThen() + or + index = 2 and result = ifStmt.getElse() + ) + or + // ExprStmt: the expression (0) + index = 0 and result = n.(ExprStmt).getExpr() + or + // Assign: value (0), targets (1..n) + exists(AssignStmt a | a = n | + index = 0 and result = a.getValue() + or + result = a.getTarget(index - 1) and index >= 1 + ) + or + // AugAssign: the operation (0) + index = 0 and result = n.(AugAssignStmt).getOperation() + or + // Walrus (`x := expr`): value (0), target (1) + exists(NamedExpr ne | ne = n | + index = 0 and result = ne.getValue() + or + index = 1 and result = ne.getTarget() + ) + or + // WhileStmt: condition (0), body (1), orelse (2) + exists(WhileStmt w | w = n | + index = 0 and result = w.getCondition() + or + index = 1 and result = w.getBody() + or + index = 2 and result = w.getElse() + ) + or + // ForeachStmt: collection (0), variable (1), body (2), orelse (3) + exists(ForeachStmt f | f = n | + index = 0 and result = f.getCollection() + or + index = 1 and result = f.getVariable() + or + index = 2 and result = f.getBody() + or + index = 3 and result = f.getElse() + ) + or + // ReturnStmt: the value (0) + index = 0 and result = n.(ReturnStmt).getExpr() + or + // AssertStmt: test (0), message (1) + exists(AssertStmt a | a = n | + index = 0 and result = a.getTest() + or + index = 1 and result = a.getMsg() + ) + or + // DeleteStmt: targets left to right + result = n.(DeleteStmt).getTarget(index) + or + // WithStmt: context expr (0), optional vars (1), body (2) + exists(WithStmt w | w = n | + index = 0 and result = w.getContextExpr() + or + index = 1 and result = w.getOptionalVars() + or + index = 2 and result = w.getBody() + ) + or + // Throw (raise): exception (0), cause (1) + exists(Throw r | r = n | + index = 0 and result = r.getExpr() + or + index = 1 and result = r.getCause() + ) + or + // TryStmt: body (0), handlers (1..n), finally (-1) + exists(TryStmt t | t = n | + index = 0 and result = t.getBody() + or + result = t.getCatch(index - 1) and index >= 1 + or + index = -1 and result = t.getFinally() + ) + or + // Switch (match): subject (0), cases (1..n) + exists(Switch m | m = n | + index = 0 and result = m.getExpr() + or + result = m.getCase(index - 1) and index >= 1 + ) + or + // Case: pattern (0), guard (1), body (2) + exists(Case c | c = n | + index = 0 and result = c.getAPattern() + or + index = 1 and result = c.getGuard() + or + index = 2 and result = c.getBody() + ) + or + // CatchClause (except handler): type (0), name (1), body (2) + exists(CatchClause h | h = n | + index = 0 and result = h.getType() + or + index = 1 and result = h.getVariable() + or + index = 2 and result = h.getBody() + ) + or + // ConditionalExpr (IfExp): condition (0), then (1), else (2) + exists(ConditionalExpr ie | ie = n | + index = 0 and result = ie.getCondition() + or + index = 1 and result = ie.getThen() + or + index = 2 and result = ie.getElse() + ) + or + // Call: func (0), positional args (1..n), keyword values (n+1..n+k) + exists(CallExpr call | call = n | + index = 0 and result = call.getFunc() + or + result = call.getPositionalArg(index - 1) and index >= 1 + or + result = call.getKeywordValue(index - 1 - call.getNumberOfPositionalArgs()) and + index >= 1 + call.getNumberOfPositionalArgs() + ) + or + // Python BinaryExpr (arithmetic, bitwise, matmul, etc.): left (0), right (1) + exists(ArithBinaryExpr be | be = n | + index = 0 and result = be.getLeft() + or + index = 1 and result = be.getRight() + ) + or + // Subscript (obj[index]): object (0), index (1) + exists(SubscriptExpr sub | sub = n | + index = 0 and result = sub.getObject() + or + index = 1 and result = sub.getIndex() + ) + or + // Attribute (obj.name): object (0) + index = 0 and result = n.(AttributeExpr).getObject() + or + // Comprehension/generator: iterable (0) + index = 0 and result = n.(Comprehension).getIterable() + or + // Tuple, List, Set: elements left to right + result = n.(TupleExpr).getElt(index) + or + result = n.(ListExpr).getElt(index) + or + result = n.(SetExpr).getElt(index) + or + // Dict: key(0), value(0), key(1), value(1), ... + exists(DictExpr d, int item | d = n | + index = 2 * item and result = d.getKey(item) + or + index = 2 * item + 1 and result = d.getValue(item) + ) + or + // Arithmetic unary (-x, +x, ~x): operand (0) + index = 0 and result = n.(ArithUnaryExpr).getOperand() + or + // Compare (a < b < c): left (0), comparators (1..n) + exists(CompareExpr cmp | cmp = n | + index = 0 and result = cmp.getLeft() + or + result = cmp.getComparator(index - 1) and index >= 1 + ) + or + // Slice (start:stop:step): start (0), stop (1), step (2) + exists(SliceExpr sl | sl = n | + index = 0 and result = sl.getStart() + or + index = 1 and result = sl.getStop() + or + index = 2 and result = sl.getStep() + ) + or + // Starred (*x): value (0) + index = 0 and result = n.(StarredExpr).getValue() + or + // Fstring: values left to right + result = n.(FstringExpr).getValue(index) + or + // FormattedValue ({expr} or {expr:spec}): value (0), format spec (1) + exists(FormattedValueExpr fv | fv = n | + index = 0 and result = fv.getValue() + or + index = 1 and result = fv.getFormatSpec() + ) + or + // Yield: value (0) + index = 0 and result = n.(YieldExpr).getValue() + or + // YieldFrom: value (0) + index = 0 and result = n.(YieldFromExpr).getValue() + or + // Await: value (0) + index = 0 and result = n.(AwaitExpr).getValue() + or + // ClassExpr: base classes left to right + result = n.(ClassDefExpr).getBase(index) + or + // FunctionExpr: defaults left to right, then kw defaults + exists(FunctionDefExpr fe | fe = n | + result = fe.getDefault(index) + or + result = fe.getKwDefault(index - fe.getNumberOfDefaults()) + ) + or + // Lambda: defaults left to right, then kw defaults + exists(LambdaExpr lam | lam = n | + result = lam.getDefault(index) + or + result = lam.getKwDefault(index - lam.getNumberOfDefaults()) + ) + or + // LogicalNotExpr: operand (0) + index = 0 and result = n.(LogicalNotExpr).getOperand() + or + // BinaryExpr (`and`/`or`): left (0), right (1) + exists(BinaryExpr be | be = n | + index = 0 and result = be.getLeftOperand() + or + index = 1 and result = be.getRightOperand() + ) + } } -private module Cfg0 = Make0; +private module Cfg0 = Make0; private import Cfg0 @@ -1268,32 +1118,32 @@ private module Input implements InputSig1, InputSig2 { class CallableBodyPartContext = Void; - predicate inConditionalContext(AstSigImpl::AstNode n, ConditionKind kind) { + predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) { kind.isBoolean() and - n = any(Ast::AssertNode a).getTest() + n = any(Ast::AssertStmt a).getTest() } private string assertThrowTag() { result = "[assert-throw]" } - predicate additionalNode(AstSigImpl::AstNode n, string tag, NormalSuccessor t) { - n instanceof Ast::AssertNode and tag = assertThrowTag() and t instanceof DirectSuccessor + predicate additionalNode(Ast::AstNode n, string tag, NormalSuccessor t) { + n instanceof Ast::AssertStmt and tag = assertThrowTag() and t instanceof DirectSuccessor } predicate beginAbruptCompletion( - AstSigImpl::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always + Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always ) { - ast instanceof Ast::AssertNode and + ast instanceof Ast::AssertStmt and n.isAdditional(ast, assertThrowTag()) and c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and always = true } - predicate endAbruptCompletion(AstSigImpl::AstNode ast, PreControlFlowNode n, AbruptCompletion c) { + predicate endAbruptCompletion(Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c) { none() } predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { - exists(Ast::AssertNode assertStmt | + exists(Ast::AssertStmt assertStmt | n1.isBefore(assertStmt) and n2.isBefore(assertStmt.getTest()) or @@ -1314,18 +1164,18 @@ private module Input implements InputSig1, InputSig2 { or // While/else: when the condition is false, flow to the else block // (if present) before the after-while node. - exists(Ast::WhileNode w, Ast::StmtListNode orelse | orelse = w.getOrelse() | - n1.isAfterFalse(w.getTest()) and + exists(Ast::WhileStmt w, Ast::BlockStmt orelse | orelse = w.getElse() | + n1.isAfterFalse(w.getCondition()) and n2.isBefore(orelse) or n1.isAfter(orelse) and n2.isAfter(w) ) or - // For/else: when the collection is empty or the loop completes normally, - // flow through the else block before the after-for node. - exists(Ast::ForNode f, Ast::StmtListNode orelse | orelse = f.getOrelse() | - n1.isAfterValue(f.getIter(), any(EmptinessSuccessor t | t.getValue() = true)) and + // For/else: when the collection is empty or the loop completes + // normally, flow through the else block before the after-for node. + exists(Ast::ForeachStmt f, Ast::BlockStmt orelse | orelse = f.getElse() | + n1.isAfterValue(f.getCollection(), any(EmptinessSuccessor t | t.getValue() = true)) and n2.isBefore(orelse) or n1.isAfter(f.getBody()) and @@ -1341,13 +1191,13 @@ import CfgCachedStage import Public /** - * Maps a new-CFG AST wrapper node to the corresponding Python AST node, if any. + * Maps a CFG AST wrapper node to the corresponding Python AST node, if any. * Entry, exit, and synthetic nodes have no corresponding Python AST node. */ -Py::AstNode astNodeToPyNode(AstSigImpl::AstNode n) { - result = n.(Ast::ExprNode).asExpr() +Py::AstNode astNodeToPyNode(Ast::AstNode n) { + result = n.asExpr() or - result = n.(Ast::StmtNode).asStmt() + result = n.asStmt() or - result = n.(Ast::ScopeNode).asScope() + result = n.asScope() }