Files
codeql/python/ql/test/library-tests/ControlFlow/evaluation-order/test_boolean.py
yoff 39e6bfc894 Python: add shared-CFG AstSig adapter (AstNodeImpl)
Preparatory refactor for the shared-CFG dataflow migration. Adds the
adapter that mediates between the Python AST and the shared
codeql.controlflow.ControlFlowGraph signature, plus the test suites
that validate the new CFG directly against this adapter. The public
facade is added in the following commit.

Library additions:

- semmle.python.controlflow.internal.AstNodeImpl — wraps Python's
  Stmt/Expr/Scope/Pattern and adds two synthetic kinds of node
  (BlockStmt for body slots, intermediate nodes for multi-operand
  boolean expressions) to satisfy the shared CFG signature.

- lib/printCfgNew.ql — debug/visualisation query for the new CFG.

- consistency-queries/CfgConsistency.ql — consistency query running
  the shared CFG's standard checks against Python.

Test additions (all driven directly off AstNodeImpl):

- ControlFlow/bindings/* — annotation-driven SSA-binding tests
  (annassign, compound, comprehension, decorated, except_handler,
  imports, match_pattern, parameters, simple, type_params,
  walrus_starred, with_stmt, dead_under_no_raise).

- ControlFlow/evaluation-order/NewCfg*.ql — mirrors of the existing
  OldCfg evaluation-order self-validation suite, run against the
  new CFG via NewCfgImpl.qll.

- Minor extensions to existing test_if.py / test_boolean.py +
  cosmetic .expected churn on a handful of OldCfg tests.

No dataflow, SSA, or production query is migrated yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-24 08:15:33 +00:00

77 lines
1.9 KiB
Python

"""Short-circuit boolean operators and evaluation order."""
from timer import test, dead
@test
def test_and_both_sides(t):
# True and X — both operands evaluated, result is X
x = (True @ t[0] and 42 @ t[1, dead(2)]) @ t[dead(1), 2]
@test
def test_and_short_circuit(t):
# False and ... — right side never evaluated
x = (False @ t[0] and True @ t[dead(1)]) @ t[1, dead(2)]
@test
def test_or_short_circuit(t):
# True or ... — right side never evaluated
x = (True @ t[0] or False @ t[dead(1)]) @ t[1, dead(2)]
@test
def test_or_both_sides(t):
# False or X — both operands evaluated, result is X
x = (False @ t[0] or 42 @ t[1, dead(2)]) @ t[dead(1), 2]
@test
def test_not(t):
# not evaluates its operand, then negates
x = (not True @ t[0]) @ t[1]
y = (not False @ t[2]) @ t[3]
@test
def test_chained_and(t):
# 1 and 2 and 3 — all truthy, all evaluated left-to-right
x = (1 @ t[0] and 2 @ t[1, dead(3)] and 3 @ t[2, dead(3)]) @ t[dead(1), dead(2), 3]
@test
def test_chained_or(t):
# 0 or "" or 42 — first two falsy, all evaluated until truthy found
x = (0 @ t[0] or "" @ t[1, dead(3)] or 42 @ t[2, dead(3)]) @ t[dead(1), dead(2), 3]
@test
def test_mixed_and_or(t):
# True and False or 42 => (True and False) or 42 => False or 42 => 42
x = ((True @ t[0] and False @ t[1, dead(2)]) @ t[dead(1), 2, dead(4)] or 42 @ t[3, dead(4)]) @ t[dead(2), dead(3), 4]
@test
def test_and_side_effects(t):
# Both functions called when left side is truthy
def f():
return 10 @ t[1]
def g():
return 20 @ t[4]
x = ((f @ t[0])() @ t[2] and (g @ t[3])() @ t[5]) @ t[6]
@test
def test_or_side_effects(t):
# Both functions called when left side is falsy
def f():
return 0 @ t[1]
def g():
return 20 @ t[4]
x = ((f @ t[0])() @ t[2] or (g @ t[3])() @ t[5]) @ t[6]