mirror of
https://github.com/github/codeql.git
synced 2026-06-24 14:17:05 +02:00
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>
77 lines
1.9 KiB
Python
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]
|