|
ESBMC
Efficient SMT-based Context-Bounded Model Checker
|
Handles Python dictionary operations for ESBMC verification. More...
#include <python_dict_handler.h>
Public Member Functions | |
| python_dict_handler (python_converter &converter, contextt &symbol_table, type_handler &type_handler) | |
| Constructs a new dictionary handler. | |
| bool | is_dict_literal (const nlohmann::json &element) const |
| Checks if a JSON AST node represents a dictionary literal. | |
| exprt | get_dict_literal (const nlohmann::json &element) |
| Creates a dictionary literal expression. | |
| exprt | get_dict_comprehension (const nlohmann::json &element) |
| exprt | build_range_dict_comprehension (const nlohmann::json &element, const nlohmann::json &generator, symbolt &dict_sym) |
| exprt | create_dict_from_literal (const nlohmann::json &element, const exprt &target_symbol) |
| Creates and initializes a dictionary from a literal expression. | |
| void | create_empty_dict (const nlohmann::json &element, const exprt &target_symbol) |
Initialises target_symbol as an empty dict located at element. | |
| exprt | create_dict_with_unpacking (const nlohmann::json &element, const exprt &target_symbol) |
| Builds a dict display holding a ** unpack ({**a, "k": v, **b}) by applying its entries in order to an empty dict. | |
| exprt | handle_dict_subscript (const exprt &dict_expr, const nlohmann::json &slice_node, const typet &expected_type=typet(), bool check_type=false) |
| Handles dictionary subscript access (e.g., dict['key']). | |
| void | check_value_type (bool enabled, const exprt &obj, const typet &type, const locationt &location) |
When enabled, assert that the PyObject obj holds a value of type. | |
| void | handle_dict_subscript_assign (const exprt &dict_expr, const exprt &key_expr, const exprt &value, const locationt &location, const nlohmann::json &node, codet &target_block) |
| Handles dictionary subscript assignment (e.g., dict['key'] =
value). | |
| exprt | handle_dict_membership (const exprt &key_expr, const exprt &dict_expr, bool negated) |
| Handles dictionary membership testing (in / not in operators). | |
| void | handle_dict_delete (const exprt &dict_expr, const nlohmann::json &slice_node, codet &target_block) |
| Handles dictionary key deletion (e.g., del dict['key']). | |
| bool | is_dict_type (const typet &type) const |
| Checks if a type represents a Python dictionary. | |
| bool | holds_dict_lists (const typet &type) const |
True when a dict value of type holds a dict's list pointers, which a byte copy into the values list does not preserve: a dict or an Optional<dict> (#9081). | |
| struct_typet | get_dict_struct_type () |
| Gets or creates the dictionary struct type. | |
| void | resolve_dict_subscript_types (const nlohmann::json &left, const nlohmann::json &right, exprt &lhs, exprt &rhs) |
| Resolve dictionary subscript types for comparisons. | |
| bool | is_retypable_subscript (const nlohmann::json &node) |
| A subscript whose base can be converted again to retype the read. | |
| typet | get_dict_value_type_from_annotation (const nlohmann::json &annotation_node) |
| Extract value type from dict type annotation. | |
| typet | get_popitem_tuple_type (const exprt &dict_expr) |
| Returns the tuple type (key_type, value_type) for dict.popitem(). | |
| exprt | handle_dict_popitem (const exprt &dict_expr, const nlohmann::json &call_node) |
| Handles dict.popitem() method calls. | |
| typet | resolve_expected_type_for_dict_subscript (const exprt &dict_expr) |
| Resolve expected type for dict subscript using variable annotation. | |
| exprt | read_struct_value (const exprt &obj_value, const typet &type) |
Read a struct value of type type from a dict item's value pointer obj_value. | |
| typet | literal_value_type (const exprt &target) |
The Optional<T> value type a literal assigned to target stores, from the target's annotation or, for an argument, the parameter's (#8016). | |
| typet | recorded_tuple_value_type (const exprt &dict_expr) const |
| Stored tuple struct type of a literal dict's values, recorded at construction; empty unless every value has the same tuple type. | |
| bool | reads_value_as_tagged (const exprt &dict_expr, const nlohmann::json &slice_node, const element_type_registry::entries &entries) |
Whether a read of dict_expr yields the stored item as a tagged scalar. | |
| char | annotated_value_kind (const exprt &dict_expr) |
's' or 'n' when dict_expr is written dict[K, V] with a str or number V, else '\0'. | |
| typet | uniform_list_dict_value_type (const std::string &list_id) const |
The scalar or str value type shared by every dict literal recorded as an element of list list_id; empty when an element is not one, the types differ, or the type is an aggregate. | |
| bool | handle_subscript_assignment_check (python_converter &converter, const nlohmann::json &ast_node, const nlohmann::json &target, codet &target_block) |
| Check and handle dictionary subscript assignment. | |
| bool | handle_literal_assignment_check (python_converter &converter, const nlohmann::json &ast_node, const exprt &lhs) |
| Check and handle dictionary literal assignment. | |
| bool | handle_unannotated_literal_check (python_converter &converter, const nlohmann::json &ast_node, const nlohmann::json &target, const symbol_id &sid) |
| Check and handle unannotated dictionary literal. | |
| exprt | handle_dict_get (const exprt &dict_expr, const nlohmann::json &call_node) |
| Handles dict.get() method calls. | |
| exprt | handle_dict_setdefault (const exprt &dict_expr, const nlohmann::json &call_node) |
| Handles dict.setdefault() method calls. | |
| exprt | handle_dict_pop (const exprt &dict_expr, const nlohmann::json &call_node) |
| Handles dict.pop() method calls. | |
| std::string | materialize_tuple_key (exprt &key_expr, const nlohmann::json &element) |
| Gives a tuple key an identity so value-reading folds can resolve it. | |
| exprt | handle_dict_update (const exprt &dict_expr, const nlohmann::json &call_node) |
| Handles dict.update() method calls. | |
| exprt | bind_dict_source (const exprt &source, const nlohmann::json &node) |
| A call returning a dict is a statement; binds its result so its members can be read. | |
| void | copy_dict_entries (const exprt &dict_expr, const exprt &source, const nlohmann::json &call_node) |
Copies every entry of source into dict_expr, overwriting keys already present (dict.update(other) semantics). | |
| exprt | handle_dict_union (const exprt &lhs, const exprt &rhs, const nlohmann::json &element) |
| Builds lhs | rhs (PEP 584) as a new dict holding lhs's entries updated with rhs's. | |
| exprt | handle_dict_copy (const exprt &dict_expr, const nlohmann::json &call_node) |
| Handles dict.copy() method calls. | |
| exprt | handle_dict_clear (const exprt &dict_expr, const nlohmann::json &call_node) |
| Handles dict.clear() method calls. | |
| exprt | handle_dict_fromkeys (const nlohmann::json &call_node) |
| Handles dict.fromkeys() class method calls. | |
| exprt | handle_dict_constructor (const nlohmann::json &call_node) |
| Handles dict(iterable) constructor calls. | |
| exprt | compare (const exprt &lhs, const exprt &rhs, const std::string &op) |
| Compares two dictionaries for equality or inequality. | |
| std::optional< bool > | try_constant_fold_eq (const nlohmann::json &lhs, const nlohmann::json &rhs) const |
| Constant-fold a dict-vs-dict equality when both AST operands are literal int-keyed, int-valued dicts (or their dict.fromkeys([...], v) equivalent). | |
Static Public Member Functions | |
| static bool | is_value_returning_method (const std::string &method_name) |
| Returns true for dict methods that return a value and emit IR. | |
| static const std::string & | get_internal_list_id (const std::string &dict_sym_id, bool keys) |
| Return the keys-list or values-list symbol id for dict_sym_id. | |
Public Attributes | |
| typet | literal_value_type_hint |
| The value type a dict literal not bound to an annotated name takes, such as one passed straight to an annotated parameter (#8016). | |
Handles Python dictionary operations for ESBMC verification.
This class is responsible for converting Python dictionary operations into equivalent C/GOTO constructs that can be processed by ESBMC's symbolic execution engine.
The handler works in conjunction with python_converter to process dictionary-related AST nodes from the Python frontend.
Key Features:
| python_dict_handler::python_dict_handler | ( | python_converter & | converter, |
| contextt & | symbol_table, | ||
| type_handler & | type_handler ) |
Constructs a new dictionary handler.
| converter | Reference to the main Python converter for expression handling. |
| symbol_table | Reference to the symbol table (context) for symbol management. |
| type_handler | Reference to the type handler for type operations. |
| char python_dict_handler::annotated_value_kind | ( | const exprt & | dict_expr | ) |
's' or 'n' when dict_expr is written dict[K, V] with a str or number V, else '\0'.
The preprocessor's guess from a literal's first value is no annotation.
A call returning a dict is a statement; binds its result so its members can be read.
Any other expression is returned as is.
| exprt python_dict_handler::build_range_dict_comprehension | ( | const nlohmann::json & | element, |
| const nlohmann::json & | generator, | ||
| symbolt & | dict_sym ) |
| void python_dict_handler::check_value_type | ( | bool | enabled, |
| const exprt & | obj, | ||
| const typet & | type, | ||
| const locationt & | location ) |
When enabled, assert that the PyObject obj holds a value of type.
Compares two dictionaries for equality or inequality.
Implements Python's dictionary comparison semantics by performing order-independent content comparison:
This correctly handles cases such as {"a": 1, "b": 2} == {"b": 2, "a": 1}
Supports both == (Eq) and != (NotEq) operators.
| lhs | Left-hand side dictionary expression |
| rhs | Right-hand side dictionary expression |
| op | Comparison operator ("Eq" or "NotEq") |
| void python_dict_handler::copy_dict_entries | ( | const exprt & | dict_expr, |
| const exprt & | source, | ||
| const nlohmann::json & | call_node ) |
Copies every entry of source into dict_expr, overwriting keys already present (dict.update(other) semantics).
| exprt python_dict_handler::create_dict_from_literal | ( | const nlohmann::json & | element, |
| const exprt & | target_symbol ) |
Creates and initializes a dictionary from a literal expression.
Generates the GOTO instructions to:
Nested Dictionary Handling: When a value is a nested dict, the implementation:
| element | The JSON AST node containing the dictionary literal. |
| target_symbol | The LHS expression (dictionary variable) to assign to. |
| exprt python_dict_handler::create_dict_with_unpacking | ( | const nlohmann::json & | element, |
| const exprt & | target_symbol ) |
Builds a dict display holding a ** unpack ({**a, "k": v, **b}) by applying its entries in order to an empty dict.
| void python_dict_handler::create_empty_dict | ( | const nlohmann::json & | element, |
| const exprt & | target_symbol ) |
Initialises target_symbol as an empty dict located at element.
| exprt python_dict_handler::get_dict_comprehension | ( | const nlohmann::json & | element | ) |
| exprt python_dict_handler::get_dict_literal | ( | const nlohmann::json & | element | ) |
Creates a dictionary literal expression.
Returns a symbol expression for a dictionary literal. For nested dictionaries, this creates a temporary variable to ensure the dict exists as a concrete symbol that can be referenced via pointer.
The actual initialization is performed by create_dict_from_literal().
| element | The JSON AST node containing the dictionary literal. |
| std::runtime_error | if the element is not a dictionary literal. |
| struct_typet python_dict_handler::get_dict_struct_type | ( | ) |
Gets or creates the dictionary struct type.
Returns the struct type used to represent Python dictionaries. If the type doesn't exist in the symbol table, it is created and added.
The dict struct contains:
| typet python_dict_handler::get_dict_value_type_from_annotation | ( | const nlohmann::json & | annotation_node | ) |
Extract value type from dict type annotation.
For annotations like dict[K, V], extracts the value type V.
| annotation_node | The annotation AST node (Subscript with slice) |
|
inlinestatic |
Return the keys-list or values-list symbol id for dict_sym_id.
Returns an empty string when the dict was not created via a literal (e.g. it is a function parameter).
Returns the tuple type (key_type, value_type) for dict.popitem().
| dict_expr | The dictionary expression (must be a symbol) |
| exprt python_dict_handler::handle_dict_clear | ( | const exprt & | dict_expr, |
| const nlohmann::json & | call_node ) |
Handles dict.clear() method calls.
Empties the dictionary in place by clearing both backing lists (keys and values). Returns nil; dict.clear() has no return value in Python.
| dict_expr | The dictionary expression to clear |
| call_node | The function call AST node |
| exprt python_dict_handler::handle_dict_constructor | ( | const nlohmann::json & | call_node | ) |
Handles dict(iterable) constructor calls.
Synthesises a Dict literal from the iterable and routes through get_dict_literal. Supported iterable forms:
Returns nil_exprt for unsupported forms. The caller falls back to generic call dispatch, which may produce broken GOTO for iterables this handler does not recognise — extend the matcher in the .cpp implementation rather than relying on the fallback for new forms.
| call_node | The function call AST node (Call(Name("dict"), [arg])). |
| exprt python_dict_handler::handle_dict_copy | ( | const exprt & | dict_expr, |
| const nlohmann::json & | call_node ) |
Handles dict.copy() method calls.
Returns a shallow copy of the dictionary: a new dict whose keys and values lists are independent copies of the source's, so mutating the copy does not affect the original.
| dict_expr | The source dictionary expression |
| call_node | The function call AST node |
| void python_dict_handler::handle_dict_delete | ( | const exprt & | dict_expr, |
| const nlohmann::json & | slice_node, | ||
| codet & | target_block ) |
Handles dictionary key deletion (e.g., del dict['key']).
Generates GOTO instructions to:
This provides proper Python semantics where deleting a non-existent key raises an error.
| dict_expr | The expression representing the dictionary. |
| slice_node | The JSON AST node for the key to delete. |
| target_block | The code block to append generated instructions to. |
| exprt python_dict_handler::handle_dict_fromkeys | ( | const nlohmann::json & | call_node | ) |
Handles dict.fromkeys() class method calls.
Implements dict.fromkeys(iterable, value=None):
| call_node | The function call AST node. |
| exprt python_dict_handler::handle_dict_get | ( | const exprt & | dict_expr, |
| const nlohmann::json & | call_node ) |
Handles dict.get() method calls.
Implements Python's dict.get(key, default=None) semantics:
| dict_expr | The dictionary expression |
| call_node | The function call AST node containing arguments |
| exprt python_dict_handler::handle_dict_membership | ( | const exprt & | key_expr, |
| const exprt & | dict_expr, | ||
| bool | negated ) |
Handles dictionary membership testing (in / not in operators).
Checks whether a key exists in the dictionary's keys list.
| key_expr | The expression representing the key to search for. |
| dict_expr | The expression representing the dictionary. |
| negated | If true, implements not in; if false, implements in. |
| exprt python_dict_handler::handle_dict_pop | ( | const exprt & | dict_expr, |
| const nlohmann::json & | call_node ) |
Handles dict.pop() method calls.
Implements Python's dict.pop(key[, default]) semantics:
| dict_expr | The dictionary expression |
| call_node | The function call AST node containing arguments |
| exprt python_dict_handler::handle_dict_popitem | ( | const exprt & | dict_expr, |
| const nlohmann::json & | call_node ) |
Handles dict.popitem() method calls.
Implements Python's dict.popitem() semantics:
| dict_expr | The dictionary expression |
| call_node | The function call AST node |
| exprt python_dict_handler::handle_dict_setdefault | ( | const exprt & | dict_expr, |
| const nlohmann::json & | call_node ) |
Handles dict.setdefault() method calls.
Implements Python's dict.setdefault(key, default=None) semantics:
| dict_expr | The dictionary expression |
| call_node | The function call AST node containing arguments |
| exprt python_dict_handler::handle_dict_subscript | ( | const exprt & | dict_expr, |
| const nlohmann::json & | slice_node, | ||
| const typet & | expected_type = typet(), | ||
| bool | check_type = false ) |
Handles dictionary subscript access (e.g., dict['key']).
Generates GOTO instructions to:
Type-Safe Retrieval:
| dict_expr | The expression representing the dictionary. |
| slice_node | The JSON AST node for the subscript key. |
| expected_type | The expected type of the value (used for proper type casting). If not specified, defaults to string (char*). Supported types: signedbv, unsignedbv, bool, floatbv, string, list, and nested dict types. |
| check_type | Assert that the stored value has expected_type, for an expected_type inferred from records a later store may not update. |
| void python_dict_handler::handle_dict_subscript_assign | ( | const exprt & | dict_expr, |
| const exprt & | key_expr, | ||
| const exprt & | value, | ||
| const locationt & | location, | ||
| const nlohmann::json & | node, | ||
| codet & | target_block ) |
Handles dictionary subscript assignment (e.g., dict['key'] = value).
Generates GOTO instructions to update or insert a key-value pair:
This provides proper Python dictionary semantics where assignment either updates or creates entries.
| dict_expr | The expression representing the dictionary. |
| slice_node | The JSON AST node for the subscript key. |
| value | The expression representing the value to assign. |
| target_block | The code block to append generated instructions to. |
| exprt python_dict_handler::handle_dict_union | ( | const exprt & | lhs, |
| const exprt & | rhs, | ||
| const nlohmann::json & | element ) |
Builds lhs | rhs (PEP 584) as a new dict holding lhs's entries updated with rhs's.
| exprt python_dict_handler::handle_dict_update | ( | const exprt & | dict_expr, |
| const nlohmann::json & | call_node ) |
Handles dict.update() method calls.
Implements Python's dict.update(other) semantics:
| dict_expr | The dictionary expression to update |
| call_node | The function call AST node containing the argument dict |
| bool python_dict_handler::handle_literal_assignment_check | ( | python_converter & | converter, |
| const nlohmann::json & | ast_node, | ||
| const exprt & | lhs ) |
Check and handle dictionary literal assignment.
| converter | Reference to python_converter |
| ast_node | Assignment AST node |
| lhs | Left-hand side expression |
| bool python_dict_handler::handle_subscript_assignment_check | ( | python_converter & | converter, |
| const nlohmann::json & | ast_node, | ||
| const nlohmann::json & | target, | ||
| codet & | target_block ) |
Check and handle dictionary subscript assignment.
Checks if the target is a dictionary subscript and handles the assignment
| converter | Reference to python_converter |
| ast_node | Assignment AST node |
| target | Target subscript node |
| target_block | Code block to append instructions |
| bool python_dict_handler::handle_unannotated_literal_check | ( | python_converter & | converter, |
| const nlohmann::json & | ast_node, | ||
| const nlohmann::json & | target, | ||
| const symbol_id & | sid ) |
Check and handle unannotated dictionary literal.
| converter | Reference to python_converter |
| ast_node | Assignment AST node |
| target | Target node |
| sid | Symbol identifier |
True when a dict value of type holds a dict's list pointers, which a byte copy into the values list does not preserve: a dict or an Optional<dict> (#9081).
| bool python_dict_handler::is_dict_literal | ( | const nlohmann::json & | element | ) | const |
Checks if a type represents a Python dictionary.
| type | The type to check. |
| bool python_dict_handler::is_retypable_subscript | ( | const nlohmann::json & | node | ) |
A subscript whose base can be converted again to retype the read.
A call to a module-level function was typed by its first conversion, and converting it again would call it twice.
|
static |
Returns true for dict methods that return a value and emit IR.
Used to avoid double-evaluation during type inference: these methods must not be called via get_expr() in create_symbol_for_unannotated_assign(), because they already emit GOTO instructions as a side-effect.
Keep in sync with handle_dict_method() when adding new value-returning dict methods.
| method_name | The method name to check. |
The Optional<T> value type a literal assigned to target stores, from the target's annotation or, for an argument, the parameter's (#8016).
| std::string python_dict_handler::materialize_tuple_key | ( | exprt & | key_expr, |
| const nlohmann::json & | element ) |
Gives a tuple key an identity so value-reading folds can resolve it.
Gives a tuple key an identity of its own, as a list literal already does for its elements.
Folds that read an element's value (sorted() over tuple keys) resolve it through the recorded element id; without one they decline and fall back to a path that retypes tuple elements as int. Returns the id to record, empty when the key has none.
Read a struct value of type type from a dict item's value pointer obj_value.
A class instance is stored by reference, as a list stores it (#7685); any other struct is stored by value.
type is not a struct | bool python_dict_handler::reads_value_as_tagged | ( | const exprt & | dict_expr, |
| const nlohmann::json & | slice_node, | ||
| const element_type_registry::entries & | entries ) |
Whether a read of dict_expr yields the stored item as a tagged scalar.
Whether a read of dict_expr at slice_node, whose recorded values are entries, yields the stored item as a tagged scalar: every value is tagged, or the read's constant key was written a string where the written dict[K, V] annotation has a number V, or the reverse.
The latter is read like an element of a list mixing strings and numbers (#4797): its type_id, not V, then answers isinstance. A key not known statically, at the read or the write, leaves the read typed V.
Stored tuple struct type of a literal dict's values, recorded at construction; empty unless every value has the same tuple type.
Homogeneous tuple values: the struct type recorded at construction (tagged and string-padded), which neither an annotation nor an AST literal peek reproduces.
| dict_expr | The dictionary expression (must be a symbol) |
Empty when the dict was not built from a literal or its value types are not a single tuple type.
| void python_dict_handler::resolve_dict_subscript_types | ( | const nlohmann::json & | left, |
| const nlohmann::json & | right, | ||
| exprt & | lhs, | ||
| exprt & | rhs ) |
Resolve dictionary subscript types for comparisons.
When comparing dict[key] with primitives, this method ensures both operands have compatible types by dereferencing dict subscripts.
Handles three cases:
| left | Left JSON AST node |
| right | Right JSON AST node |
| lhs | Left operand expression (modified in place) |
| rhs | Right operand expression (modified in place) |
Resolve expected type for dict subscript using variable annotation.
Looks up the dict variable's annotation to determine what type the subscript operation should return.
| dict_expr | The dictionary expression (must be a symbol) |
| std::optional< bool > python_dict_handler::try_constant_fold_eq | ( | const nlohmann::json & | lhs, |
| const nlohmann::json & | rhs ) const |
Constant-fold a dict-vs-dict equality when both AST operands are literal int-keyed, int-valued dicts (or their dict.fromkeys([...], v) equivalent).
Replaces the otherwise-quadratic __ESBMC_dict_eq runtime model call for the common literal-vs-literal pattern, which is the dominant cost under --incremental-bmc when each k iteration re-symbolises the comparison from scratch.
Returns nullopt when either operand falls outside the recognised literal shape; callers must then fall back to the runtime model. Only int keys and int values are recognised today: string / nested / mixed-type dict literals fall through unchanged.
| lhs | Left-hand AST node (Compare.left or BinOp.left). |
| rhs | Right-hand AST node (Compare.comparators[0] or BinOp.right). |
| typet python_dict_handler::uniform_list_dict_value_type | ( | const std::string & | list_id | ) | const |
The scalar or str value type shared by every dict literal recorded as an element of list list_id; empty when an element is not one, the types differ, or the type is an aggregate.
| typet python_dict_handler::literal_value_type_hint |
The value type a dict literal not bound to an annotated name takes, such as one passed straight to an annotated parameter (#8016).