From 971beb2d89a7b8ac6a53cfbaf62429d5a3740945 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 21 Apr 2026 14:40:24 +0000 Subject: [PATCH] Python: Assert statements Co-authored-by: yoff --- .../controlflow/internal/AstNodeImpl.qll | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll index 2d68b2cb69b..a68a01f50a9 100644 --- a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll +++ b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll @@ -10,6 +10,7 @@ private import python as Py private import codeql.controlflow.ControlFlowGraph +private import codeql.controlflow.SuccessorType private module Ast { /** The newtype representing AST nodes for the shared CFG library. */ @@ -204,6 +205,17 @@ private module Ast { 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 `try` statement. */ class TryNode extends StmtNode { private Py::Try tryStmt; @@ -461,6 +473,13 @@ module AstSigImpl implements AstSig { // 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 // ThrowStmt (raise): the exception (0), the cause (1) exists(Ast::RaiseNode r | r = n | index = 0 and result = r.getException() @@ -827,17 +846,50 @@ private module Input implements InputSig1, InputSig2 { string toString() { result = "label" } } + predicate inConditionalContext(AstSigImpl::AstNode n, ConditionKind kind) { + kind.isBoolean() and + n = any(Ast::AssertNode 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 beginAbruptCompletion( AstSigImpl::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always ) { - none() + ast instanceof Ast::AssertNode and + n.isAdditional(ast, assertThrowTag()) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = true } predicate endAbruptCompletion(AstSigImpl::AstNode ast, PreControlFlowNode n, AbruptCompletion c) { none() } - predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { none() } + predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Ast::AssertNode assertStmt | + n1.isBefore(assertStmt) and + n2.isBefore(assertStmt.getTest()) + or + n1.isAfterTrue(assertStmt.getTest()) and + n2.isAfter(assertStmt) + or + n1.isAfterFalse(assertStmt.getTest()) and + ( + n2.isBefore(assertStmt.getMsg()) + or + not exists(assertStmt.getMsg()) and + n2.isAdditional(assertStmt, assertThrowTag()) + ) + or + n1.isAfter(assertStmt.getMsg()) and + n2.isAdditional(assertStmt, assertThrowTag()) + ) + } } import CfgCachedStage