Files
codeql/python/ql/test/library-tests/ControlFlow/evaluation-order/test_basic.py
Taus 71cd5be513 Python: Add self-validating CFG tests
These tests consist of various Python constructions (hopefully a
somewhat comprehensive set) with specific timestamp annotations
scattered throughout. When the tests are run using the Python 3
interpreter, these annotations are checked and compared to the "current
timestamp" to see that they are in agreement. This is what makes the
tests "self-validating".

There are a few different kinds of annotations: the basic `t[4]` style
(meaning this is executed at timestamp 4), the `t[dead(4)]` variant
(meaning this _would_ happen at timestamp 4, but it is in a dead
branch), and `t[never]` (meaning this is never executed at all).

In addition to this, there is a query, MissingAnnotations, which checks
whether we have applied these annotations maximally. Many expression
nodes are not actually annotatable, so there is a sizeable list of
excluded nodes for that query.
2026-05-12 12:42:29 +00:00

224 lines
5.2 KiB
Python

"""Basic expression evaluation order.
These tests verify that sub-expressions within a single expression
are evaluated in the expected order (typically left to right for
operands of binary operators, elements of collection literals, etc.)
Every evaluated expression has a timestamp annotation, except the
timer mechanism itself (t[n], t.dead[n]).
"""
from timer import test, never
@test
def test_sequential_statements(t):
"""Statements execute top to bottom."""
x = 1 @ t[0]
y = 2 @ t[1]
z = 3 @ t[2]
@test
def test_binary_add(t):
"""In a + b, left operand evaluates before right."""
x = (1 @ t[0] + 2 @ t[1]) @ t[2]
@test
def test_binary_subtract(t):
"""In a - b, left operand evaluates before right."""
x = (10 @ t[0] - 3 @ t[1]) @ t[2]
@test
def test_binary_multiply(t):
"""In a * b, left operand evaluates before right."""
x = ((3 @ t[0]) * (4 @ t[1])) @ t[2]
@test
def test_nested_binary(t):
"""Sub-expressions evaluate before their containing expression."""
x = ((1 @ t[0] + 2 @ t[1]) @ t[2] + (3 @ t[3] + 4 @ t[4]) @ t[5]) @ t[6]
@test
def test_chained_add(t):
"""a + b + c is (a + b) + c: left to right."""
x = ((1 @ t[0] + 2 @ t[1]) @ t[2] + 3 @ t[3]) @ t[4]
@test
def test_mixed_precedence(t):
"""In a + b * c, all operands still evaluate left to right."""
x = (1 @ t[0] + ((2 @ t[1]) * (3 @ t[2])) @ t[3]) @ t[4]
@test
def test_string_concat(t):
"""String concatenation operands: left to right."""
x = (("hello" @ t[0] + " " @ t[1]) @ t[2] + "world" @ t[3]) @ t[4]
@test
def test_comparison(t):
"""In a < b, left operand evaluates before right."""
x = (1 @ t[0] < 2 @ t[1]) @ t[2]
@test
def test_chained_comparison(t):
"""Chained a < b < c: all evaluated left to right (b only once)."""
x = (1 @ t[0] < 2 @ t[1] < 3 @ t[2]) @ t[3]
@test
def test_list_elements(t):
"""List elements evaluate left to right."""
x = [1 @ t[0], 2 @ t[1], 3 @ t[2]] @ t[3]
@test
def test_dict_entries(t):
"""Dict: key before value, entries left to right."""
d = {1 @ t[0]: "a" @ t[1], 2 @ t[2]: "b" @ t[3]} @ t[4]
@test
def test_tuple_elements(t):
"""Tuple elements evaluate left to right."""
x = (1 @ t[0], 2 @ t[1], 3 @ t[2]) @ t[3]
@test
def test_set_elements(t):
"""Set elements evaluate left to right."""
x = {1 @ t[0], 2 @ t[1], 3 @ t[2]} @ t[3]
@test
def test_subscript(t):
"""In obj[idx], object evaluates before index."""
x = ([10 @ t[0], 20 @ t[1], 30 @ t[2]] @ t[3])[1 @ t[4]] @ t[5]
@test
def test_slice(t):
"""Slice parameters: object, then start, then stop."""
x = ([1 @ t[0], 2 @ t[1], 3 @ t[2], 4 @ t[3], 5 @ t[4]] @ t[5])[1 @ t[6]:3 @ t[7]] @ t[8]
@test
def test_method_call(t):
"""Object evaluated, then attribute lookup, then arguments left to right, then call."""
x = (("hello world" @ t[0]).replace @ t[1])("world" @ t[2], "there" @ t[3]) @ t[4]
@test
def test_method_chaining(t):
"""Chained method calls: left to right."""
x = ((((" hello " @ t[0]).strip @ t[1])() @ t[2]).upper @ t[3])() @ t[4]
@test
def test_unary_not(t):
"""Unary not: operand evaluated first."""
x = (not True @ t[0]) @ t[1]
@test
def test_unary_neg(t):
"""Unary negation: operand evaluated first."""
x = (-(3 @ t[0])) @ t[1]
@test
def test_multiple_assignment(t):
"""RHS evaluated once in x = y = expr."""
x = y = (1 @ t[0] + 2 @ t[1]) @ t[2]
@test
def test_callable_syntax(t):
"""t(value, n) is equivalent to value @ t[n]."""
x = (1 @ t[0] + 2 @ t[1]) @ t[2]
y = (x @ t[3] * 3 @ t[4]) @ t[5]
@test
def test_subscript_assign(t):
"""In obj[idx] = val, value is evaluated before target sub-expressions."""
lst = [0 @ t[0], 0 @ t[1], 0 @ t[2]] @ t[3]
(lst @ t[5])[1 @ t[6]] = 42 @ t[4]
x = lst @ t[7]
@test
def test_attribute_assign(t):
"""In obj.attr = val, value is evaluated before the object."""
class Obj:
pass
o = (Obj @ t[0])() @ t[1]
(o @ t[3]).x = 42 @ t[2]
y = (o @ t[4]).x @ t[5]
@test
def test_nested_subscript_assign(t):
"""Nested subscript assignment: val, then outer obj, then keys."""
d = {"a" @ t[0]: [0 @ t[1], 0 @ t[2]] @ t[3]} @ t[4]
(d @ t[6])["a" @ t[7]][1 @ t[8]] = 99 @ t[5]
x = d @ t[9]
@test
def test_unreachable_after_return(t):
"""Code after return has no CFG node."""
def f():
x = 1 @ t[1]
return x @ t[2]
y = 2 @ t[never]
result = (f @ t[0])() @ t[3]
@test
def test_none_literal(t):
"""None is a name constant."""
x = None @ t[0]
y = (x @ t[1] is None @ t[2]) @ t[3]
@test
def test_delete(t):
"""del statement removes a variable binding."""
x = 1 @ t[0]
del x
y = 2 @ t[1]
@test
def test_global(t):
"""global statement allows writing to module-level variable."""
global _test_global_var
_test_global_var = 1 @ t[0]
x = _test_global_var @ t[1]
@test
def test_nonlocal(t):
"""nonlocal statement allows inner function to rebind outer variable."""
x = 0 @ t[0]
def inner():
nonlocal x
x = 1 @ t[2]
(inner @ t[1])() @ t[3]
y = x @ t[4]
@test
def test_walrus(t):
"""Walrus operator := evaluates the RHS and binds it."""
if (y := 1 @ t[0]) @ t[1]:
z = y @ t[2]