Skip to content
Development Update: End of August 2026

Development Update: End of August 2026

August 26, 2026

A short follow-up to the late-August update: roughly 80 pull requests landed on master between 22 and 26 August. Here are the ones a user will notice.

Bitwuzla now uses its native floating-point theory. #7022 implements the SMT floating-point theory on the Bitwuzla backend — the default solver — so floats are encoded with fp.add and friends instead of ESBMC’s own bit-vector lowering. On symbolic-input FP queries it measured 1.2–13× faster, and 303 FP-touching regression tests agree on every verdict. --fp2bv opts back into the old encoding; fmod/remainder keep a bit-vector round-trip because fp.rem is two orders of magnitude slower on the solver side. One known gap: the theory cannot represent the sign of a NaN (#7021).

A cross-run proof cache. #7143 adds --proof-cache <dir>, which keys every discharged claim on a hash of its sliced SSA cone, the build and the options that affect what is verified, and skips the solver on a hit. Only proved (UNSAT) claims are stored; --proof-cache-verify solves anyway and reports any mismatch. The full write-up is on the new Proof cache page.

Verdicts and property tables that were missing. A plain BMC run under --show-cex recorded no verdict and printed “0 properties failed” above VERIFICATION FAILED (#7258); --k-induction-parallel skipped the per-property table — and with it the CWE rows — that sequential --k-induction prints (#7262). An unknown --function on a .goto binary now reports an error rather than aborting (#7263), and a goto binary produced by another tool prints in C syntax instead of crashing the counterexample printer (#7264). The SV-COMP wrapper was taught to read the per-property table introduced by #7064, which had been turning ~2500 correct-false verdicts into Unknown (#7250).

--dead-code-check reports the dead direction and fewer false advisories. The advisory named the guard of the live branch; it now names the direction that is actually unreachable (#7295). It no longer reports CWE-561 on if (1) (#7277), on code reachable only through a call inside a constant-folded Python assert (#7279), or on the IndexError / KeyError / ZeroDivisionError guards and exception-propagation edges the Python frontend inserts itself (#7307).

Pointer arithmetic below an object. Three stacked fixes (#7222, #7224, #7228) make p - k on a void * move in the right direction, report a p[-1] underflow that a wrapping offset used to hide, and compare pointer offsets signed so that p >= b is false for p = b - 1. A harness that walks for (p = end; p >= begin; p--) over a struct or scalar now reports out of bounds, as it already did over an array. Every multi-byte heap write on a big-endian target was byte-reversed against the read that stitched it back (#7276).

C++ base subobjects under ESBMC’s own layout. The thunk adapting a Base* receiver and both arms of dynamic_cast sized their adjustment with clang’s record layout, but ESBMC lays classes out itself, so under the Itanium primary-base rule dispatch and member access disagreed on which object they were touching (#7243). A derived-to-base conversion onto a non-first base under a virtual base is now displaced, so the base’s methods, constructor and destructor address the right storage (#7241, #7292). string_view became a literal type and gained its std::string conversion (#7215, #7253), and timegm is modelled beside mktime (#7273).

Python: closures, dunders and dynamic typing. A nested def reading an enclosing function’s scalar is bound to a captured cell rather than an unconstrained frame local (#7297); writing c[0] += 1 from a nested function mutates the enclosing list (#7225, #7227). Callable[[A], R] annotations carry their signature, so a call through the value is no longer nondet (#7298). Explicit __getitem__, __len__ and __contains__ calls dispatch to their operators (#7316). Dynamic typing now covers elif chains and -// between two tagged operands (#7281). Call-site parameter inference iterates to a fixpoint, fixing a spurious CWE-469 on a comparison of two unannotated parameters (#7257), and a comprehension target that shadows an outer variable gets its own name (#7235).

Python: sorted() and friends stop lying. sorted(), min() and max() with a key= the preprocessor cannot fold used to drop the key silently and answer in natural order; they are now refused with a named error (#7313). sorted(), reversed() and list() keep their argument’s element type, so a list of tuples still unpacks after sorting (#7310), and slicing a dict view keeps its element types too (#7318). A subscript on the right-hand side of an assignment emits one bounds check rather than two — a 20% drop in symex assignments on a double-subscript loop (#7309).

NumPy views. Row views (a[0], a[-1]), column views (a[:, j]), strided and reversed 1-D slices (a[::2], a[::-1]), np.diagonal, np.ravel and a.flat[i] are now pointer-backed views onto the base buffer, so writes through either side are observed by the other and len, .shape and .ndim report the view’s own extent (#7193, #7261). np.trace and np.fill_diagonal reuse the same offset arithmetic.

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.