Skip to content
Development Update: Late August 2026

Development Update: Late August 2026

August 22, 2026

A follow-up to the previous update: another 175 or so pull requests landed on master between 13 and 22 August. Here are the highlights.

Every run names every property. #7064 prints the per-property ** Results: block on every run, not only under --multi-property, and --result-only no longer suppresses it — so esbmc --result-only file.c now names what it checked. Properties a single-property run never separated are reported NOT CHECKED rather than PASSED, since a run that stops at the first violation cannot decide the rest.

Python gets dynamic typing. A variable whose type diverges across an if/else is carried as a tagged value instead of being forced to one branch’s type. Building on #6535, the mechanism now covers reassignment and arithmetic (#7018), isinstance (#7117, #7179), is and == comparisons (#7135), rebinding to a container (#7178) and function return values (#7162) — most of which previously aborted the frontend or crashed symbolic execution outright.

Contracts frame what they name. An __ESBMC_assigns clause naming an array now frames the whole array rather than element [0] (#7019), a multi-level path such as o->sub->a is rooted at its parameter instead of generating zero verification conditions (#7068), and the element-level frame rule was corrected to evaluate its index in the pre-state (#7069, #7107, #7217). __ESBMC_old works under a quantifier (#6964), a named object satisfies __ESBMC_is_fresh (#7036), and a replaced call’s ensures now speaks about the value the call returned (#7033). On the Python side, __ESBMC_old is lowered for scalars (#7038), completing the scalar contract MVP (#6942).

A large C++ standard-library push. Around fifty operational-model pull requests landed. New this fortnight: std::reference_wrapper with ref/cref (#7147, #7213), std::unordered_multiset (#7156), <chrono> clocks and time_point (#6985), std::put_time and std::placeholders (#7132), filesystem::u8path and the path string accessors (#7169, #7180), and a long list of <type_traits> additions. The containers took their Allocator template parameter (#7163, #7167), std::function’s call target is templated on its signature (#7171), and basic_string’s size, resize, max_size and rfind were brought into line with the standard (#7145, #7175).

Nothing in /tmp for C and C++ runs. #6517 serves the bundled clang headers, C++ operational models and internal libc to clang from memory instead of extracting them — 320 files and 7.1 MB of /tmp traffic per C run, 488 files and 8.0 MB per C++ run, both now zero, with wall clock unchanged. The operational models are compiled straight out of the same store (#6766) and the Python models moved to their own blob (#7058). Python and Solidity still extract, since a forked python3 or solc can only read real files.

A bound on the whole stack. #6970 adds --total-stack-limit, which sums the live frames rather than bounding one at a time, so a recursion whose individual frames each fit is caught. ESBMC’s own operational models are excluded, so the bound stays calibratable against a real stack budget.

Soundness and precision. The pointer analysis resolves pointers an aggregate holds at an offset (#6981) and reports misaligned reads from pointer arrays (#6979) — both were false SUCCESSFUL results on ordinary C. A pointer whose object id is a free variable can no longer be placed at address zero (#6999). fmod, remainder and remquo lower to the solver’s exact FP remainder instead of x - y*(int)(x/y), which returned 64.0 for fmod(1e18, 3.0) (#7015). Two backends that used to end a run with no verdict now report one (#7065, #7078).

Python type inference and NumPy. A bare list parameter, or an unannotated one, recovers its element type from the call sites (#7192, #7199), including in an imported module (#7198). try/finally runs the cleanup on the return/break/continue edges (#7181), len() on a class without __len__ raises TypeError instead of measuring the struct with strlen and answering 0 — which had silently emptied for loops (#7131, #7137) — and a generator’s if clauses survive being consumed by sum, min, max or sorted (#7214). In NumPy, arange is materialised at conversion time (#7020), logical calls chain as arguments to other calls (#7089), and a 1-D unit-stride slice is a real view onto the base array rather than a copy (#7144).

The website documentation has been updated to match. As always, the full list is in the commit history — and if you hit a bug or a missing feature, we would love an issue report.