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() }