C++ Limitations
Note: The limitations below apply to the current version of ESBMC’s C++ frontend. Many are actively being addressed — check the
C++issue label for the latest status. For what is supported, see C++ Support.
Constructor and destructor ordering
The order in which constructors and destructors run is not correct in every case, tracked by the umbrella issue #940. The affected cases are:
- Virtual destructors
- Base initialization for the most-derived class
- Order of destruction under object composition (part-whole relationships)
A program whose correctness depends on one of these orderings may verify when it should not, or vice versa.
A function-local static array with a dynamic initialiser is still initialised
before main, so its initialiser’s side effects happen even if the function is
never called. Other dynamically initialised local statics are initialised on
the first pass through their declaration, as [stmt.dcl]/3 requires.
Containers
inserton a nestedstd::vectordoes not converge under an unbounded strategy.std::vector<std::vector<int>>now constructs its elements correctly, andpush_backconverges under--incremental-bmc, but the element copy-constructor loop thatinsert’s shift runs unwinds indefinitely. Use a bounded--unwind Nrun for that case.deque(iterator, iterator)has no allocator-taking form. Every otherlistanddequeconstructor now pairs with one ([list.cons], [deque.cons]), sostd::list<int, A> c(a)parses. The iterator-pairdequeconstructor is held back because its size has a separate off-by-one: accepting the standard spelling would turn a compile error into a silently wrong answer.- Comparing two
std::listiterators with<is rejected, as it is against libc++: a list iterator is not random-access, so the ordering has to come from a user-declaredoperator<. What changed is that such a user-declared operator is now found by argument-dependent lookup. - Some STL container regression tests remain marked
KNOWNBUG(#4400); theregression/esbmc-cppsuites are the authoritative record of which specific cases fail.
Exceptions
- Under
--lower-exceptions, apthreadstart routine reached through a computed function pointer — or one that is both called directly and used as a start routine — cannot be given a sound per-function uncaught-escape check, and is declined as unsupported. Declining is sound: ESBMC never validates a buggy program as a result, it reports that it cannot analyse it. - Some exception-handling regression tests remain marked
KNOWNBUG(#4402).
Inheritance and polymorphism
Base-subobject displacements — the override thunk adapting a Base* receiver,
both arms of dynamic_cast, and derived-to-base conversions under a virtual
base — are now taken from ESBMC’s own class layout rather than Clang’s
ASTRecordLayout, which disagreed with it under the Itanium primary-base rule
(#3894). A hierarchy containing a
virtual base keeps a flattened layout that cannot express every shape; the
remaining ones are pinned as KNOWNBUG under
regression/esbmc-cpp/inheritance/. Some
inheritance/polymorphism regressions remain marked KNOWNBUG
(#4399), as do some of the
gcc-template-tests (#4398).
Expressions
An lvalue conditional over reference variables — (c ? ra : rb).x = 2 —
aborts the frontend, and a conditional over pointers to members is unsupported
(#6717). The reference-returning
call shape works.
Standard library
- Headers listed under
Not modelled —
<ranges>,<format>,<print>,<coroutine>,<generator>,<barrier>and<stdfloat>— have no operational model.--mix-cpp-host-headerslets an unmodelled include fall through to your host header, but ESBMC then has to verify the real implementation, which is frequently intractable. - Three modelled headers decline to compute what they describe, so a property
that depends on the result cannot be proved:
<regex>answers every match nondeterministically,<syncstream>transfers no characters, and<codecvt>performs no conversion.<charconv>has the integral overloads only, and<concepts>omits the cross-type_withconcepts and theinvocablefamily rather than approximate them. <version>promises only the eight feature-test macros ESBMC can honour. A program that branches on a macro it leaves undefined takes the feature-unavailable path, which is the conservative answer but not always the one a host build takes.<latch>,<semaphore>and<stop_token>leave out theconstexprconstructors, the timed acquires (there is no clock to time them against) andjthread.- Mixing the operational models with host headers can produce ambiguity errors
for names defined by both, such as
char_traitsandistream(#3387). - The operational models are deliberately simplified abstractions of the real library (#965). They are written for verification tractability, so their performance characteristics and internal representations do not match a production standard library.
std::filesystem::directory_iteratoryields a bounded, nondeterministic number of synthesised entries — nothing reads a real filesystem, so symbolic execution terminates. The cap is an assumption rather than an assertion, so a defect needing more entries than it allows is excluded silently, where the container models assert instead and reportcapacity exceeded.std::ilogb,std::logbandstd::nexttowardresolve as overloads but have no model in ESBMC’s libc, so they return a nondeterministic value rather than the C99 result.
Time and clocks
system_clock::now() and steady_clock::now() read a shared counter that
advances by a non-negative nondeterministic step, so a reading is not wall-clock
time and the gap between two readings is unconstrained. Checking a program
against a particular instant needs an __ESBMC_assume on the value, and one
whose correctness depends on how much real time passed cannot be checked at all.
system_clock::period follows the target platform, so the range a time_point
represents — and the point at which it saturates — differs between Linux, Apple
and Windows.
Because the counter is shared, system_clock is monotone too, even though its
is_steady is false as the standard allows. A defect that needs the system
clock to jump backwards — an NTP correction, an operator resetting the clock —
is therefore outside what the model can produce.
Calendar and time-zone facilities are absent: the C++20 types
(year_month_day, zoned_time, utc_clock), the chrono_literals suffixes
(10ms), and the floor / ceil / round / abs duration helpers.
std::this_thread::sleep_for and sleep_until are absent too — ESBMC already
interleaves at every step, so a sleep would not constrain the schedule it
explores.
std::ratio is complete — <ratio> carries [ratio.syn] in full, including
ratio_add, ratio_subtract, the six comparison aliases with their C++17 _v
variables, and the SI typedefs from atto to exa.
Standard version
The default is C++17. C++20 and C++23 features require an explicit --std — for
example --std c++20. The operational models honour --std only where a
version guard has been added by hand — see
Standard-version guards for the
headers that have one; the models are not systematically versioned
(#1678).
Bounded verification
These are properties of bounded model checking rather than of the C++ frontend, but they shape what a C++ result means:
- Loops are unwound to a finite bound. A plain
--unwind Nrun proves the absence of bugs only up toNiterations; use--incremental-bmcor--k-inductionto seek an unbounded proof. - ESBMC targets x86_64 by default; other target architectures are not yet
generically supported (#1585).
A target clang no longer maps —
--ppc-macos, whose triple it rejects as unknown — is reported asPARSING ERRORcarrying clang’s own diagnostic, rather than crashing ESBMC.