Supported Features
This page is a reference of all Python language constructs, data structures, and standard library modules currently supported by ESBMC-Python.
Basic Constructs
- Control flow:
if/elif/else,for(withrange()),while,for ... elseandwhile ... else(theelseclause runs when the loop completes withoutbreak; both are lowered via a did-not-break flag) - Arithmetic:
+,-,*,/,//,%,** - Logical operations:
and,or,not - Identity comparisons:
is,is not(includingx is None,x is not None) - Tuple-unpacking assignment:
a, b = b, aand cross-binding forms likea, b = b, a % bevaluate the entire right-hand side before binding any target (Python’s parallel-assignment semantics), so swaps and idioms such as the Euclidean-GCD loopwhile b: a, b = b, a % bare lowered correctly. The simple non-cross-binding shape (x, y = 1, 2) uses direct assignment. Unpacking targets may be subscripts or attributes (a[i], b.x = ...), and may be nested ((a, b), c = ((1, 2), 3), including over a runtime right-hand side such as a for-loop element or function return:for (u, v), w in items:). - Walrus operator (PEP 572
:=): assignment expressions in the contexts where the target is evaluated exactly once — anif/elifcondition (if (n := len(data)) > 2:), a standalone assignment expression (x = (y := 5)), and a comprehension filter ([d for v in data if (d := v * 2) > 4]). The expression evaluates to the bound value. Use insideand/oroperands andwhile-loop conditions is refused with a clear diagnostic (see Limitations). - None handling: Proper type distinction from
int,bool,str, etc.; correctly falsy in boolean contexts (None and True→None,None or 1→1) - Global variables: The
globalkeyword for accessing and modifying global scope from within functions - Context managers:
withandasync withstatements via preprocessor desugaring into explicit__enter__/__exit__calls:with EXPR as VAR: BODY— binds the return value of__enter__()toVARwith EXPR: BODY— context manager used without variable bindingwith A as a, B as b: BODY— multiple context managers in one statement; expanded left-to-right,__exit__called in reverse orderasync withis handled identically towith- Exception suppression: when the body raises,
__exit__(type, value, traceback)is called and the exception is re-raised iff the return value is falsy, matching CPython semantics. This covers staticreturn True, conditional returns (return self.flag,return et is ValueError), implicit-Nonebodies (propagate by default), and single-level base-class inheritance of__exit__.
Functions and Methods
- Function definitions: Parameters, return values, and calls
- Variadic parameters:
*argssyntax for variable-length positional argument lists - Type annotations: Basic types (
int,float,bool,str),Any,Union[T1, T2], PEP 604T1 | T2syntax (includingT | Noneon class attributes) - Union types: Both
Union[int, bool]andint | boolsyntax are supported, including chained unions with multiple members - Type widening: ESBMC selects the widest type from Union members using the hierarchy
float > int > bool - Any type: When a function is annotated
-> Any, ESBMC infers the actual return type by analyzing return statements in the function body; supportsint,float,bool, and expressions evaluating to those types - Variable inference: Variables annotated with
Anythat are assigned from function calls inherit the function’s inferred return type Optional[T]equality: Equality (==,!=,is,is not) between anOptional[T]value and a matching primitive succeeds after anis not Noneround-trip — the primitive side is implicitly cast to the pointer-backed representation. Ordered comparisons (<,>,<=,>=) onOptional[T]are deliberately disabled (they would compare addresses, not values).Optional[T]return type: A function annotated-> Optional[T](thetyping.Optionalsubscript form) lowers to aT*pointer withNoneencoded asNULL, so a body that returnsNoneon some path verifies correctly.Nonecomparisons (is,is not,==,!=) applied directly to such a call —f() is None,f() == None— are evaluated against the function’s return type rather than collapsed to a constant. (The dedicatedOptional<T>struct representation is used for the PEP 604T | Noneannotation with primitiveT, not for theOptional[T]subscript form.)- Lambda expressions: Single-expression lambdas with multiple parameters; converted to regular functions and stored as function pointers; can be assigned to variables and called indirectly
Object-Oriented Programming
- Classes: Definitions, methods, and attributes
- Class attributes: Class-level variables shared across all instances; supports both explicit type annotations and automatic type inference from assigned values; accessible via both
instance.attrandClassName.attr - PEP 604 attribute annotations:
self.x: T | None(and otherT1 | T2BinOpannotations) are recognised and mapped to the same pointer-to-Tencoding used forOptional[T] - Instance variables: Attributes defined in
__init__ - Object reference semantics: When an instance attribute is assigned an aliased class-instance reference (e.g.
self.head = headfrom a constructor parameter), the field is stored as a reference, so mutating the object through one binding is visible through the attribute (and vice versa). This makes linked-list, queue, and tree patterns that reassign such attributes through chained references (curr = q.head; curr = curr.nxt; q.head = curr) verify correctly. A fresh-constructor RHS (self.a: A = A()) is still constructed in place by value. - Return-by-reference for class instances: A function whose return annotation resolves to a user-defined class returns a
Cls*reference to the heap-allocated object rather than a value copy, so the returned object survives the callee frame and preserves identity/aliasing across the call boundary (y = f(x); y.v = 1is observed throughxwhenfreturns its argument). This matches CPython and the pointer representation already used for locals, parameters, andself;return self/return paramandreturn ClassName(...)are all handled. - Self-referential instance attributes: When an attribute set in
__init__from aparam=None-defaulted parameter (e.g.self.successor = successor) is populated at construction time (Node(2, a)), its field type is recovered by unifying the matching positional constructor argument across module-levelClassName(...)calls — enabling linked-list / tree patterns and multi-level attribute chains such asc.successor.successor. This also works when the class is imported from another module (from node import Node): the attribute types are inferred across the module boundary, so a nested read likenode.successor.valueon an imported-class instance resolves. - Inheritance: Single and multi-level inheritance; verification of scenarios involving overridden methods
super()calls:super().__init__(...)and othersuper().method(...)calls, enabling verification of polymorphic behavior and parent-constructor side effects- Explicit base-class
__init__: unbound parent-constructor calls of the formBase.__init__(self, ...)(the pre-super()idiom) are dispatched to the base constructor withselfbound correctly @propertygetters: reading a@property-decorated attribute (obj.area) invokes the decorated getter method rather than looking up a struct field; inherited properties resolve through the base class- Classes as first-class values: a class name passed as a bare value (
register(Twist)) — the class object itself, not an instance — is modelled as an opaque placeholder for inert uses (storing/forwarding the class), while normal construction through the name (Twist()) is unaffected - Class-method defaults:
Namedefaults referencingESBMC_default_*helpers are hoisted past the enclosingClassDefso they remain visible at call sites
String Formatting and Literals
- Basic variable interpolation:
f"Hello {name}!"; multiple variables in one f-string - Built-in variable access in f-strings:
f"Running as: {__name__}" - Integer format specs:
f"{num:d}",f"{num:i}" - Float format specs:
f"{val:.2f}",f"{price:.1f}" - Boolean formatting: automatic conversion to
True/Falsestrings - Empty and literal f-strings:
f"",f"Just a string" - F-string concatenation with other strings
- IEEE 754–compliant 32-bit and 64-bit float-to-string conversion
Data Structures
Lists
append(x): Add element to endclear(): Remove all elementspop([i]): Remove and return element at index (default: last)remove(x): Remove first occurrence of valuecopy(): Return a shallow copyextend(iterable): Append all elements from an iterable (list, string, tuple, or function-call result)reverse(): Reverse in placesort(): Sort in place (no arguments; key/reverse parameters not supported)insert(i, x): Insert at position; handles index at/beyond end, within bounds, and empty listscount(x): Return the number of occurrences of a valueindex(x[, start[, end]]): Return the position of the first occurrence of a value; the optionalstart/endbounds search the slicel[start:end]and return the absolute index (CPython slice-clamping semantics), raisingValueErrorif not foundinoperator: Membership testing (2 in [1, 2, 3]), including membership of a user-class instance by identity (obj in [obj])del l[i]: Remove the element at a constant index- Slice assignment:
l[i:j] = srcand the extended forml[i:j:k] = src, including grow/shrink replacement (step 1), step > 1 (CPython requires matching lengths), and negative step (l[::-1] = src) +operator: List concatenation ([1,2] + [3,4])- Repetition:
lst * nwith both literal and variable lists - Nested lists: Method calls on subscripted elements (e.g.,
nested[i].append(v)) - Typed instance attributes:
self.attr: List[T]instance attributes with full method support
Strings
Predicates: startswith(), endswith(), isspace(), isalpha(), isdigit(), islower(), isupper(), isalnum(), isnumeric(), isidentifier()
startswith()/endswith() also accept a tuple of affixes (s.startswith(("ab", "x")) is true if s matches any element) and the optional position arguments s.startswith(prefix, start[, end]), evaluated as s[start:end].startswith(prefix) with Python slice clamping over constant receivers.
Case conversion: lower(), upper(), capitalize(), title(), swapcase(), casefold()
Search: find(), rfind(), index(), rindex(), count() (with optional range arguments). index()/rindex() are the raising forms of find()/rfind() — they return the first/last position and raise ValueError when the substring is absent (rather than returning -1).
Modification: replace(), strip(), lstrip(), rstrip(), removeprefix(), removesuffix(), translate(str.maketrans(...)) (constant-folded over ASCII operands; the two- and three-argument maketrans forms map and delete characters, matching CPython)
Splitting/joining: split(), rsplit(), splitlines(), partition(), rpartition(), join()
Padding: center(), ljust(), rjust(), zfill(), expandtabs()
Formatting: format() with {}, {0}, {name} placeholders; format_map() with constant dicts
Slicing: s[start:end], omitted bounds (s[:end], s[start:]), negative indices (s[-3:]), empty slices
Operators: in (substring test), * (repetition: "a" * 3, 3 * "a", boolean multipliers)
Comparison: ==, !=, and the ordered comparisons <, <=, >, >= over constant strings, evaluated lexicographically by code point (characters compared as unsigned bytes, so high-bit characters order as in CPython)
Non-constant receivers: Calls with a non-constant string receiver no longer abort GOTO conversion. Three layers cooperate to give a sound result:
- Constant folding (symex layer). When the receiver and arguments are compile-time constants — either as literals or after AST-level const-propagation of a single
Assign/AnnAssignin the enclosing function — the result folds to an exact value. Folded methods includeswapcase,upper,lower,casefold,capitalize,title,isalpha,isdigit,isalnum,isspace,isupper,islower,startswith,endswith,count,find,rfind,index,strip,lstrip, andrstrip. - Runtime operational models. A growing set of
strmethods lower to bounded operational models insrc/c2goto/library/python/string.crather than a bare nondet. These include the case transformslower(),upper(),swapcase(),capitalize(),title()(__python_str_lower/_upper/_swapcase/_capitalize/_title), the predicatesisupper(),islower(),isalpha(),isdigit(),isalnum(),isspace(), the countercount(sub), and the searchesfind()/rfind(). Concrete arguments fold via symex’s constant propagation; symbolic receivers get a real symbolic count, predicate, or returned string rather than an unconstrained nondet. The string-returning models cap the receiver at ~255 characters (a 256-byte buffer): an over-length receiver trips an explicit assertion (e.g.String too long for swapcase() - exceeds 255 characters), exceptupper(), which truncates at the buffer bound.str.join(iterable)also lowers to a runtime model (__python_str_join, 511-character result bound) when its iterable is a variable whose initialiser cannot be folded (e.g. aList[str]parameter): an empty list yields""and a non-empty list is concatenated element-by-element with the separator. - Nondet fallback. For all other string methods, a non-constant receiver yields a sound symbolic value (nondet
char *,bool, orint) instead of aborting.partition()falls back to the 3-tuple("", "", "")solen(t) == 3holds;splitlines()falls back to an empty list.format()andformat_map()follow the same shape when their format string or arguments are non-constant.join()falls back to a nondetchar *when its iterable argument is not a literal list that can be folded at conversion time (e.g.sorted(...), a list comprehension, or a function-call result), so such calls convert instead of aborting. The result is a sound over-approximation: specific functional values are not preserved, but safety checks downstream remain meaningful.
Sets
- Literals:
{1, 2, 3} - Empty set:
set()(note:{}creates an empty dict, not a set) - From iterable:
set(list),set(str),set(d.keys()),set(d.values()) - Operators:
-(difference),&(intersection),|(union),^(symmetric difference, equivalent to.symmetric_difference()); the augmented assignment^=is supported too - Methods:
issubset(other),issuperset(other),symmetric_difference(other),update(other), and the method formsunion(other),intersection(other),difference(other)(non-mutating; they route to the same builders as the|/&/-operators and return a fresh set). Subset/superset relations are evaluated directly over the operand lists (a set-materialization bypass), soset(iterable).issuperset(...)works without first building the set. - Membership:
x in s,x not in s - Equality:
s1 == s2,s1 != s2(order-independent) len()built-in
Tuples
- Literals:
(1, 2, 3),(),(5,)(single-element) - Mixed types:
(42, "hello", 3.14) - Nested tuples:
((1, 2), (3, 4)) - Constant-index access with bounds checking:
t[0],t[2] - Generic annotation (
t: tuple) and parameterized annotation (-> tuple[int, int]) - Equality comparison:
t1 == (1, 2, 3) - Ordering comparison (
<,<=,>,>=): element-wise lexicographic, matching CPython ((1, 2) < (1, 3)) tuple(...)constructor:tuple(t)returns the tuple unchanged;tuple(list)(a literal, variable, or list-returning call such assorted(...)) builds a shallow copy of the list;tuple("ab")over a constant string yields a tuple of single-character strings (('a', 'b')).tuple(bytes)is rejected with a clean error (CPython would produce a tuple of ints).len()built-inisinstance(obj, tuple)type checking
Dictionaries
- Literals:
{"a": 1, "b": 2} - Subscript access:
d["a"]; raisesKeyErrorif absent - Subscript assignment:
d["c"] = 3 - Membership:
"a" in d,"a" not in d - Deletion:
del d["a"]; raisesKeyErrorif absent - Equality:
d1 == d2(order-independent) - Iteration:
forloops overd.keys(),d.values(),d.items(), and directly over the dict (for k in d:). For a local dict literal with tuple keys, the destructuring formfor u, v in d:is also supported — each key is unrolled as a tuple literal so it unpacks correctly. Iteration, dict comprehensions, and.items()unpacking over an unannotated parameter dict are sound for scalar keys and integer-tuple keys — the concretedict[K, V]is recovered (scope-aware) from the call sites, and an ambiguous shape stays a clean error rather than a wrong guess. String-tuple-keyed parameter dicts remain a known gap (#5571). update(other): Merge another dictget(key[, default]): Return value or default; returnsOptional[T]when no default is providedsetdefault(key[, default]): Insert key with default if absent, then return value; supportsint,float,bool,strpop(key[, default]): Remove and return value; raisesKeyErrorif absent and no defaultpopitem(): Remove and return last inserted(key, value)pair; raisesKeyErrorif emptyclear(): Remove all entries in place; the dict stays usable afterwards (len(d) == 0)- Nested dicts:
dict[int, dict[int, int]] Optional[T]values:dict[str, Optional[T]]storage and retrieval- Dict comprehensions:
{k: v for ...}is lowered to an empty dict plus a population loop. Supported iterables includerange(...)(constant or symbolic bound), a list of tuples, andd.items()with a(key, value)tuple target ({k: v + 1 for k, v in d.items()}), with optionaliffilters. Subsequent key lookups return the populated values rather than raisingKeyError.
Complex Numbers
- Literals:
3+4j,1j,0j - Constructor:
complex(real, imag)withint,float, orboolarguments - Attributes:
.real,.imag(read-onlyfloat) - Methods:
.conjugate()— returns complex conjugate - Arithmetic:
+,-,*,/,**; augmented assignment (+=,-=,*=,/=) - Promotion:
int,float, andbooloperands are automatically promoted tocomplex abs(z): Returns the magnitude as afloat(IEEE-754 hypot)- Boolean context:
bool(z)isFalseonly when both.realand.imagare0.0(signed-zero aware) - Equality:
==,!=; ordering operators (<,<=,>,>=) and//,%raiseTypeError - Annotations:
z: complex;Optional[complex],Union[complex, float]
Enum Module (enum)
Enumbase class: members storevalue: intandname: str- Comparison:
==,!= - Hash:
__hash__()returnsvalue - Representation:
__str__()and__repr__()returnname
Bytes and Integers
Byte sequences and integer class methods:
bytes(...)constructor —bytes(iterable-of-ints)(e.g.bytes([1, 2, 3])) andbytes(n)(nzero bytes) build a real byte array, like ab"..."literal, solen()and indexing work; byte literals (b"abc") are also supportedint.from_bytes(bytes_data, byteorder, *, signed=False)— converts a byte sequence to an integer; supports big- and little-endian, signed and unsigned. Endianness may be given positionally (int.from_bytes(b, "big")) or as thebyteorder=keyword (int.from_bytes(b, byteorder="big"))int.to_bytes(length=1, byteorder='big', *, signed=False)— converts an integer to a byte sequence.lengthandbyteordermay each be passed positionally or by keyword, and both default (CPython 3.11+:(5).to_bytes()→ one big-endian byte). A non-constantbyteorderis rejected with a clean error rather than silently defaulting to big-endianbytes.hex([sep[, bytes_per_sep]])— constant-folds a literalbytesobject to its hex string. With the optional one-charactersep(and optionalbytes_per_sepgroup size) it reproduces CPython grouping exactly:bytes([1, 2, 3]).hex("-")→"01-02-03",bytes([0xb9, 0x01, 0x9e, 0xf3]).hex("_", 2)→"b901_9ef3"(positive group size counts from the right, negative from the left)bytes.fromhex(s)— constant-folds a hex string to abytesobject (the inverse of.hex()); accepts upper/lowercase digits and ASCII whitespace between byte pairs, and raises CPython’sValueErroron odd-length or non-hex inputbytes.startswith/endswith/find/rfindover literal operands — folded directly over the byte-array representation when the receiver (and affix/sub argument) are literalbytes([...])constructors, sobytes([1,2,3]).endswith(bytes([2,3]))isTrueandbytes([1,2,3]).find(bytes([2,3]))is1.find/rfindalso accept a single integer byte. Non-literal receivers,b"..."literals, and the position-argument forms fall through to the existing dispatch unchangedint.bit_length(n)— returns the number of bits required to representnin binary. The operational model bounds the loop length by512, which covers narrow 64-bitIntWideand 512-bit--irbignum receivers and guarantees termination on symbolicnwithout an explicit--unwind.int.conjugate()— returns the integer unchanged (the conjugate of a real integer is itself; part of the numeric-tower API)- Numeric-tower properties —
int.numerator/int.denominator(anintis the ration/1),int.real/int.imag, andfloat.real/float.imag.float.numerator/float.denominatorand these properties on abooldeliberately raise a cleanAttributeError(CPython’sfloatis not aRational). float.is_integer()— constant-folds on a literal float receiver (e.g.(2.0).is_integer()→True), evaluated asisfinite(d) && d == trunc(d)to match CPythonstr.encode()/bytes.decode()— standalone constant-folded conversions over ASCII data (s.encode()→ byte array of ordinals,b.decode()→ string of byte values); a non-ASCII / multi-byte character falls through to a clean error, matching CPython’sUnicodeDecodeError. The round-trip forms.encode().decode()is also supported.
Error Handling
try/exceptblocks with multiple handlers andexcept ExceptionType as varbindingtry/finallyblocks: thefinallybody runs on normal completion, after a caught exception, and when an exception propagates uncaught (runfinally, then re-raise). Baretry/finally(noexcept) is supported. Shapes that cannot be lowered soundly are refused with a clean diagnostic (see Limitations)raisestatements with exception instantiation and custom messages; bareraisere-raises the active exception inside anexcepthandlerassertstatements for property verification__ESBMC_assumefor constraining non-deterministic inputsImportErrorguards: Imports insidetry/except ImportErrorare handled statically
Built-in exception hierarchy:
BaseException→Exception→AssertionError,ValueError,TypeError,IndexError,KeyError,ZeroDivisionError,ImportErrorOSError→FileNotFoundError,FileExistsError,PermissionError
Exception instances expose a message attribute and support __str__().
Concurrency (threading module)
ESBMC-Python lowers threading primitives onto ESBMC’s existing pthread operational model in src/c2goto/library/pthread_lib.c, so symex’s interleaving exploration, deadlock detection, and data-race detection apply.
threading.Lock
Lock.acquire() and Lock.release() mirror pthread_mutex_lock_noassert via __ESBMC_atomic_begin / __ESBMC_assume / __ESBMC_atomic_end. Supported usage:
lock = threading.Lock()at module scope or as a class instance attributelock.acquire()/lock.release()- Multiple
Lockinstances co-existing on the same object (e.g. pairedmutexandlockfields) from threading import Lockaliasing
threading.Thread
For each Thread(target=f, args=(...)) construction site, the frontend synthesises a per-call-site trampoline __pythread_trampoline_<N> plus three helpers in pthread_lib.c (__pyt_init_tid, __pyt_terminate, __pyt_join) that mirror pthread_create / pthread_join bookkeeping. The resulting GOTO program is a direct call (not a function pointer), so symex preserves precision.
Supported Thread shapes:
target=f—fmust be a function statically resolvable at the construction site (aNameor attribute chain). Lambdas, runtime-callable values, andThread-subclassing’srunoverride are out of scope.args=(...)— must be a tuple literal whose elements are expressions evaluable at the construction site. Passing simple values (ints, floats, bools, strings) works end-to-end.t.start()andt.join()— lower topthread_createandpthread_joinsemantics;joinestablishes happens-before.- Multiple construction sites per program, with independent trampolines.
Data-race detection
Python module-level globals (declared with x: T = … or x = … at module scope) are flagged in the symbol table so they are visible to ESBMC’s race-assertion pass. Two threads writing to the same global without synchronisation are detected under --data-races-check:
import threading
shared: int = 0
def writer_a() -> None:
global shared
shared = 1
def writer_b() -> None:
global shared
shared = 2
t1 = threading.Thread(target=writer_a)
t2 = threading.Thread(target=writer_b)
t1.start(); t2.start(); t1.join(); t2.join()esbmc race.py --incremental-bmc --data-races-check reports W/W data race on py:race.py@shared and VERIFICATION FAILED.
Cover Properties and Reachability
__ESBMC_cover(cond) checks whether a condition is satisfiable at a given program point (inverted assertion semantics: a counterexample means the condition is reachable).
Use with --multi-property to report all reachable/unreachable points without stopping at the first result.
x: int = __VERIFIER_nondet_int()
if x > 0:
__ESBMC_cover(x > 100) # Is this branch reachable?Strict Type Checking
The --strict-types flag enables type compatibility validation for function arguments at verification time. When a type mismatch is detected, a TypeError is generated with a descriptive message. Instance methods, class methods, and static methods are all handled with appropriate implicit-parameter awareness.
Code Quality Analysis
- Missing return statement detection: Statically detects non-void functions lacking return statements on all paths; reports as verification failures; excludes
__init__automatically
Module System
__name__: Set to"__main__"when run directly; set to the module name when imported. Enablesif __name__ == "__main__":idioms.- Imports: Standard
importandfrom ... import ...styles validated at verification time - Local bindings shadow imported modules: When a name is both an imported module and a local binding (e.g. a parameter
nodewhilefrom node import Nodeis in scope), attribute access such asnode.valueresolves to the local binding, following Python’s LEGB rule, rather than to a module member. - Selective imports preserve module-level constants:
from M import f, Cretains plainAssignbindings such asINT_BOUND = 1024in addition toAnnAssignones. Tuple-unpacking targets are treated atomically. - Parser package layout: The Python parser ships as a package under
src/python-frontend/parser/(entrypointparser/__main__.py, public facadeparser/__init__.py, import resolution inparser/import_resolver.py). The resolver emits deterministic, review-friendly diagnostics for missing modules, cyclic imports, and relative-import rewrites.
Built-in Functions
| Function | Notes |
|---|---|
abs, divmod | Standard arithmetic |
int, float, bool, chr, ord, str, repr, hex, oct, bin, ascii | Type conversions and representations. bin, hex, and oct accept non-literal integer arguments: a compile-time-foldable expression (e.g. bin(round(3.0))) folds to the exact literal, while a genuinely symbolic operand (a function parameter or variable) lowers to a runtime operational model (__python_int_to_{bin,hex,oct}) producing the correctly prefixed string (0b/0x/0o, a leading - for negatives, lowercase hex digits); a non-integer argument still raises TypeError. bin is LLONG_MIN-safe; ascii emits \xNN/\uNNNN/\UNNNNNNNN escapes for non-ASCII codepoints. |
pow(b, e) | Shares the ** operator lowering (integer, float, bool operands) |
pow(b, e, m) | 3-argument modular exponentiation: exact BigInt for constant integer operands; symbolic operands raise an unsupported diagnostic rather than emit unsound floating-point modulo |
format(value[, spec]) | Builtin formatting (distinct from the str.format() method). Constant-folds a literal integer with a bare presentation-type spec ('d'/'x'/'X'/'o'/'b' or empty) — format(255, "x") → "ff" (no 0x/0o/0b prefix, leading - for negatives, LLONG_MIN-safe) — and a constant string with the default spec to itself. Width/alignment/precision specs, float values, and variable arguments raise a clean error rather than a wrong fold |
callable(obj), issubclass(cls, base) | Resolved at compile time from the symbol table and AST class hierarchy |
len | Works on lists, sets, strings, tuples |
range | Used in for loops |
min(a, b), max(a, b) | Two-argument form only; promotes int to float |
min([...]), max([...]) | Single-list form; supports int, float, and str element types. The key= argument is honoured over constant lists for the lambda x: x[K], key=abs, and key=len forms (the winning element is returned; ties break toward the first occurrence) |
sum([...]) | Sum of list elements; supports int and float |
sum(range(EXPR)) | Single-arg sum of a single-arg range is rewritten to the Gauss closed form EXPR * (EXPR - 1) // 2 if EXPR > 0 else 0, yielding an exact value (and 0 for EXPR <= 0) instead of a nondet result |
sorted(iterable) | Returns a new sorted list; supports int, float, and str elements |
any([...]) | List literals only; short-circuit OR logic |
all([...]) | List literals only; short-circuit AND logic |
enumerate(iterable, start=0) | Tuple unpacking and single-variable forms; optional start |
zip(a, b, ...) | Lowered to an index-based while loop in for form, mirroring enumerate |
reversed(iter) | Lowered to an index-based while loop in for form; reversed(range(...)) is rewritten to an equivalent forward range(...) |
filter(pred, iter) | Lowered to an index-based while loop guarded by pred in for form |
list() | Zero-arg constructor lowers to an empty list literal. list(iterable) over a list, range, or tuple (list((2, 3)), list(t)) builds a real list; list("abc") over a constant string yields a list of single-character strings. list(bytes) is rejected with a clean diagnostic (CPython would produce a list of ints). |
isinstance(obj, type) | Runtime type checking |
float("nan"), float("inf") | Special values (case-insensitive, whitespace-tolerant) |
input() | Modelled as nondeterministic string, max 256 chars |
print(...) | Arguments evaluated for side effects; no output produced |
Complex Math Module (cmath)
All functions accept complex, float, int, or bool arguments. Real inputs are promoted to complex automatically.
Constants: pi, e, tau, inf, nan, infj, nanj
Conversion: phase(z), polar(z) → (r, φ), rect(r, φ) → complex
Power/log: exp(z), log(z[, base]), log10(z), sqrt(z)
Trigonometric: sin, cos, tan, asin, acos, atan
Hyperbolic: sinh, cosh, tanh, asinh, acosh, atanh
Utilities: isnan(z), isinf(z), isfinite(z), isclose(a, b[, rel_tol, abs_tol])
Math Module (math)
Constants: pi, e, inf, tau, nan
Rounding: floor(x), ceil(x), trunc(x), fabs(x), modf(x)
Power/log: exp(x), expm1(x), exp2(x), log(x), log1p(x), log2(x), log10(x), pow(x, y)
Trigonometric: sin, cos, tan, asin, acos, atan, atan2
Hyperbolic: sinh, cosh, tanh, asinh, acosh, atanh
Integer helpers: factorial(n), gcd(*ints), lcm(*ints) (variadic — any number of integer arguments, including the 0- and 1-argument forms, folded via the binary model and working on symbolic operands), isqrt(n), perm(n[, k]), comb(n, k), prod(lst[, start]) (expects list[int])
Geometry: hypot(x, y), dist(p, q) (expects list[float])
Utilities: fmod(x, y), remainder(x, y), copysign(x, y), degrees(x), radians(x), isclose(a, b), isfinite(x), isnan(x), isinf(x)
Advanced: cbrt(x), erf(x), erfc(x), gamma(x), lgamma(x), frexp(x) → (mantissa, exponent), ldexp(x, i), nextafter(x, y), ulp(x), sumprod(a, b) (expects list[float]), fsum(values) (expects list[float])
Regular Expression Module (re)
Functions: re.match(pattern, string), re.search(pattern, string), re.fullmatch(pattern, string)
Supported pattern features:
- Universal match:
.* - Empty patterns and literal strings (no metacharacters)
- Character class ranges with quantifiers:
[a-z]+,[A-Z]+,[0-9]* - Digit sequences:
\d+,\d* - Alternation:
(x|y)z* - Prefix-with-wildcard: patterns ending with
.*
Match results: Usable for Boolean/None testing (if re.match(...), re.search(...) is not None). Group-capture methods (.group(), .groups(), .span()) are not supported.
Type validation: Both arguments must be string or bytes-like; invalid types raise TypeError.
Verification approach: Uses operational models combining direct pattern recognition, literal string matching, and nondeterministic behavior for complex patterns.
Random Module (random)
All functions are modelled using nondeterministic values with appropriate constraints via __ESBMC_assume, allowing ESBMC to explore all possible values within the specified ranges.
random.random()→ nondeterministicfloatin[0.0, 1.0)random.uniform(a, b)→ nondeterministicfloatN wheremin(a,b) ≤ N ≤ max(a,b)random.randint(a, b)→ nondeterministicintN wherea ≤ N ≤ brandom.getrandbits(k)→ nondeterministic non-negativeintin[0, 2**k − 1]; raisesValueErrorifk < 0; returns0whenk == 0random.randrange(start[, stop[, step]])→ randomly selected integer from the specified range; single-argument form (randrange(stop)) is also supportedrandom.choice(seq)→ nondeterministic elementseq[i]for a constrained index; raisesIndexErroron an empty sequencerandom.sample(population, k)→ under-approximation that returns the firstkelements ofpopulation; raisesValueErrorifk < 0ork > len(population)random.shuffle(lst)→ under-approximation; leaves the list untouchedrandom.seed(a=0)→ no-op; nondet outputs already cover any seed-dependent outcome
See also: Random Operational Model
Collections Module (collections)
defaultdict(default_factory): Dict subclass that returns a default value for missing keys; modelled as a plaindictwith a nondeterministic default. When the dict has no value annotation, the value type is inferred from the factory or from a subscript assignment in the enclosing function: built-in type factories (defaultdict(int),float,bool,str), nullarylambdafactories whose body is a constant or a built-in constructor call (defaultdict(lambda: float('inf'))), and constant literal assignments (d[k] = 5,0.0,True,"x") all map to the matching value type, somin/max/comparisons overd[k]no longer fall back tochar *Counter: Mapping of elements to integer counts; supports__getitem__,__setitem__,values(), and boolean truthinessdeque: List-backed double-ended queue; supports construction, indexing,__setitem__,append(), and the FIFO-front methodspopleft()(front pop) andappendleft()(front insert), enabling FIFO/BFS patterns. Aliased imports such asfrom collections import deque as Queueresolve correctlyOrderedDict: Supports construction and basic indexing /append/__setitem__
Queue Module (queue)
A single-threaded verification model: queue.Queue is backed by a plain list (FIFO) and queue.LifoQueue by a list-backed stack (LIFO). Both the qualified form (queue.Queue()) and from queue import LifoQueue work.
Queue(FIFO):put(item)→ append,get()→ pop front, in insertion orderLifoQueue(LIFO / stack):put(item)→ append,get()→ pop back- Shared methods:
qsize(),empty(),full(),put_nowait(),get_nowait();task_done()andjoin()are accepted no-ops maxsize: tracked byfull()(Queue(2));put()does not block on it
The blocking semantics of put()/get() (the block/timeout arguments) are not modelled — there is nothing to block on under sequential symbolic execution. An unguarded get() on an empty queue pops from an empty list, reported as an IndexError; guard with empty()/qsize() first.
Datetime Module (datetime)
datetime.datetime(year, month, day): Constructs a datetime object with fieldsyear,month,day,hour(0),minute(0),second(0),microsecond(0)
Decimal Module (decimal)
Decimalclass with full arithmetic:- Comparison:
==,!=,<,<=,>,>= - Arithmetic:
+,-,*,/,//,% - Unary:
-d(negation),abs(d) - Special values: infinity and NaN (via
is_specialflag; propagated through all operations)
- Comparison:
Heapq Module (heapq)
All functions operate on plain Python lists used as min-heaps.
heapify(heap): No-op in the model (heap invariant assumed)heappush(heap, item): Appends item to the heap listheappop(heap): Removes and returns the minimum elementheappushpop(heap, item): Pushes then pops the minimum
Time Module (time)
Functions use a monotonic counter model.
time.time(): Returns a monotonically increasingfloat(increments by 1.0 each call)time.sleep(seconds): Validatesseconds >= 0; no actual delay
OS Module (os)
All os functions use nondeterministic modelling to verify both success and failure paths.
Path: os.path.exists(path), os.path.basename(path)
Directory operations:
os.makedirs(path, exist_ok=False): Creates directory tree; supportsexist_okos.mkdir(path): Creates single directory; may raiseFileExistsErroros.rmdir(path): Removes empty directory; may raiseOSErroros.listdir(path): Lists directory contents
File operations:
os.remove(path): Removes file; may raiseFileNotFoundErroros.popen(cmd): Opens a pipe (modelled for verification)
NumPy Module (numpy)
Partial executable support for list-backed arrays, element-wise arithmetic, selected math functions, and small determinants. Some APIs remain stubs for type inference only.
Array construction: np.array(l), np.zeros(shape), np.ones(shape) for supported 1D/2D shapes, including explicit constructor dtype coercion for literal bool, int, and float inputs; np.arange(n), np.full(shape, value), np.eye(N[, M]), np.identity(n), and np.linspace(start, stop, num)
Slicing: bounded 1-D slicing a[i:j] on a list-backed array returns a new list[T] (bounded, open-ended a[i:]/a[:j], and full-copy a[:] forms), with the slice typed as the element type rather than collapsing to a scalar
Reductions: np.sum(a), np.prod(a), np.min(a), np.max(a), np.mean(a), np.argmin(a), np.argmax(a) over list-backed arrays
Comparison and logical ufuncs: np.greater, np.greater_equal, np.less, np.less_equal, np.equal, np.not_equal, np.logical_and, np.logical_or, np.logical_not, and np.where(cond, a, b) (element-wise select; also the scalar-condition form np.where(False, 1, 2))
Element-wise arithmetic: np.add(a, b), np.subtract(a, b), np.multiply(a, b), np.divide(a, b), np.power(a, b) on literal list-backed inputs, with NumPy-style broadcasting for 1D/2D shapes
Complex elements: element-wise complex arithmetic (add/subtract/multiply/divide) on complex scalars and arrays, plus np.conjugate(z) and .real/.imag on complex results; division by zero is reported. Complex determinants are rejected (see Limitations)
Math: np.ceil(x), np.floor(x), np.fabs(x), np.sqrt(x), np.trunc(x), np.round(x), np.rint(x), np.copysign(x, y), np.fmin(x, y), np.fmax(x, y), np.remainder(x, y), np.nextafter(x, y), np.sin(x), np.cos(x), np.tan(x), np.arcsin(x), np.arctan(x), np.arccos(x), np.sinh(x), np.cosh(x), np.tanh(x), np.exp(x), np.log(x), np.log2(x), np.log10(x), np.isclose(a, b) on scalar or literal list-backed 1D/2D inputs. np.arccos additionally lowers a runtime 1D array through the libm operational model; a runtime 2D arccos is still rejected. The two-output helpers np.modf(x) → (frac, int) and np.frexp(x) → (mantissa, exponent) are also supported.
Modulo: np.fmod(x, y) on scalars and on literal list-backed 1D/2D inputs with NumPy-style broadcasting. Operands wrapped in np.array(...) are rejected with Unsupported operation: numpy.fmod on array operands rather than mis-folded to a scalar.
Element-wise float ufuncs: np.add/np.subtract/np.multiply/np.divide on float arrays dispatch to a typed *_double operational model instead of reinterpreting IEEE-754 payloads as int64. The --python-no-fold flag suppresses the frontend’s constant-folding paths and forces SMT encoding (useful for differential testing of the folder against the encoder).
Linear algebra:
np.dot(a, b),np.matmul(a, b): 1D/2D inputs with both integer and float backends (vialinalg.c), including symbolic elements. The integer path carries the operand dtype width and asserts each result element fits the dtype range, so narrow dtypes (int16/int32) flag accumulation overflow (trivially satisfied for the default 64-bitint; combine with--overflow-checkfor int64-level overflow)np.transpose(a): 2D arrays, including runtime array variables (1D is the identity); higher-rank transpose is rejectednp.linalg.det(a): constant numeric 2x2 and 3x3 matrices (complex-valued matrices are rejected)