Skip to content

[Individual PRs to follow] C++ standard support: C++11 through C++26 parser, type-checker, and STL improvements - #8878

Draft
tautschnig wants to merge 3091 commits into
diffblue:developfrom
tautschnig:cpp11-parser-rework-squashed
Draft

tautschnig wants to merge 3091 commits into
diffblue:developfrom
tautschnig:cpp11-parser-rework-squashed

Conversation

@tautschnig

Copy link
Copy Markdown
Collaborator

This branch contains comprehensive improvements to CBMC's C++ front-end, covering parser, type-checker, template instantiation, GOTO conversion, and standard library model support for C++11 through C++26.

Summary of changes:

  • Parser: Lambdas, range-for, variadic templates, braced-init-lists, decltype, noexcept, constexpr/consteval, structured bindings, if constexpr, fold expressions, concepts, requires clauses, three-way comparison, designated initializers, coroutines, deducing this, pack indexing, contracts (pre/post)
  • Type-checker: Template partial specialization, SFINAE, forwarding references, implicit conversions, constexpr evaluation, access control, virtual dispatch, defaulted/deleted functions, GCC type traits builtins
  • Template instantiation: Variadic packs, template aliases, member templates, CTAD, concept subsumption
  • Library models: operator new/delete, allocator_traits, iterator, math classification functions, coroutine builtins
  • Infrastructure: --cpp14/--cpp17/--cpp20/--cpp23/--cpp26 flags, --stdlib option for libc++ support, <:: digraph fix, system header error suppression
  • Tests: 536 CORE regression tests, 1 KNOWNBUG (C++20 modules)

This branch will be split into smaller PRs for review. See CPP_SUPPORT_STATUS.md for a detailed feature matrix.

  • Each commit message has a non-empty body, explaining why the change was made.
  • Methods or procedures I have added are documented, following the guidelines provided in CODING_STANDARD.md.
  • The feature or user visible behaviour I have added or modified has been documented in the User Guide in doc/cprover-manual/
  • Regression or unit tests are included, or existing tests cover the modified code (in this case I have detailed which ones those are in the commit message).
  • n/a My commit message includes data points confirming performance improvements (if claimed).
  • My PR is restricted to a single feature or bugfix.
  • n/a White-space or formatting changes outside the feature-related changed lines are in commits of their own.

@tautschnig tautschnig self-assigned this Mar 17, 2026
@tautschnig
tautschnig force-pushed the cpp11-parser-rework-squashed branch 10 times, most recently from ab675bb to 4f0b085 Compare March 18, 2026 22:35
@codecov

codecov Bot commented Mar 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.53%. Comparing base (d1451f2) to head (50ea70b).

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #8878      +/-   ##
===========================================
+ Coverage    80.90%   82.53%   +1.63%     
===========================================
  Files         1718     1733      +15     
  Lines       190266   231146   +40880     
  Branches        73       82       +9     
===========================================
+ Hits        153939   190785   +36846     
- Misses       36327    40361    +4034     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@tautschnig
tautschnig force-pushed the cpp11-parser-rework-squashed branch 10 times, most recently from 8f4e2d7 to f3d208e Compare March 31, 2026 08:28
@tautschnig
tautschnig force-pushed the cpp11-parser-rework-squashed branch 6 times, most recently from 5cc8d28 to 980d519 Compare April 3, 2026 23:29
@tautschnig
tautschnig force-pushed the cpp11-parser-rework-squashed branch 2 times, most recently from 6347e6c to b5975fe Compare April 28, 2026 09:27
tautschnig and others added 30 commits October 16, 2026 10:40
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [dcl.enum]/2 allows `enum class E : bool'; to_integer accepted only
bit-vector underlying types, so such a constant as a template argument
failed "expected to be scalar constant" (also on develop).
std::ranges::subrange_kind is one; GCC 14's <format> reaches it once
__literal_encoding_is_unicode is evaluated.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…ized

N5008 [temp.deduct.partial]/8, /11: a function parameter pack deduces from
every remaining parameter of the other template, so a template whose whole
parameter-type-list is one pack (libc++'s fall-back `__nat
__invoke(_Args&&...)') is less specialized than one with a leading non-pack
parameter (`__invoke(_Fp&&, _Args&&...)').  Partial ordering compared
equal-length lists only, and the call was ambiguous (also on develop).
With libc++ 19 every ranges algorithm with a projection reached it:
__get_property and __estimated_width in <format> and std::apply lost
their bodies (macOS CI job); with --stdlib libc++ no body of round 185's
libc++ 19 translation units is dropped now.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
With the per-test timeout the macOS 14 job named the problem: no single
test hangs except two libstdc++ tests that with libc++ need a std::function
call model or 7.7 GB (now gcc-only); the remaining tests take about
2900 s run serially, too close to the 4800 s TIMEOUT.  The CORE profile is
now cbmc-cpp-1..4-CORE (add_sharded_test_pl_profile, test.pl --shard),
which ctest -j runs in parallel; locally the four take 304 s wall-clock
for 950 s of tests.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [dcl.fct]/6, [over.match.funcs]/4: a member function with an
explicit object parameter has no implicit object parameter; for a const
object `this Self &&self' deduces Self as const S &.
member_template_object_viable rejected such a member template because it
is not const-qualified.  GCC 16's ranges adaptor closures
(_Partial::operator()(this _Self&&, _Range&&)) are invoked on const
objects by std::__invoke.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [temp.func.order]/6.4: when partial ordering leaves two function
templates equally specialized, the more constrained one
([temp.constr.order]) is more specialized.  The overload set of a libc++
customization point object (`template <class T> requires ... auto
operator()(T &&)' next to `void operator()(auto &&) const = delete')
was ambiguous; it becomes reachable once abbreviated member function
templates are recognized.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [dcl.fct]/22: a function with a placeholder parameter type is an
abbreviated function template, members included.  The rewrite into a
template ran for namespace-scope declarations only, so a constructor
subrange(auto __first, _Sent __last) kept an `auto' parameter and
{first, last} found no constructor.  GCC 14's <format>
_M_parse_fill_and_align (`_Utf32_view<subrange<iterator>>
__uv({__first, __last})') lost its body once eede475 let
__literal_encoding_is_unicode evaluate (the gcc-14 CI job's three
failures).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
check-clang-format formats the test with the repository's style (its
concept definitions differ from the c++20 style used before).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
A function whose class-type result gets a hidden result parameter changes
type; only direct calls were updated.  Its address taken elsewhere (N5008
[conv.func]/1, e.g. a function pointer stored in a table) kept the old
type and failed --validate-goto-model.  Such references now carry the new
type, cast back to the original pointer type; calls through function
pointers have been resolved to direct calls already.

cpp17_abstract_environment_tu (CBMC's own abstract_environment.cpp, whose
static map stores assume_not) verifies again and is THOROUGH.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [stmt.dcl]/3: a block-scope variable with static storage duration
that is not constant-initialized ([basic.start.static]/2) is initialized
the first time control passes through its declaration.  An expression
initializer stayed the symbol's value, so __CPROVER_initialize evaluated
it at program start, with undefined parameters and `this'.  It is now
assigned under the guard static_local_initialization already uses for
constructor calls.

This was the remaining failure of std::regex_match and std::regex
construction (libstdc++'s _AnyMatcher::operator(), `static auto __nul =
_M_translator._M_translate(0)'); both tests verify again and are THOROUGH.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
The job runs test.pl directly, so the -X broken-libstdcxx16 that
regression/cbmc-cpp's CMakeLists.txt and Makefile add for gcc 16 did not
apply; its four failures in CI run 4 were exactly those tests.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
… map

N5008 [temp.inst]/4, [dcl.spec.auto.general]/15: a member of a class
template specialization is instantiated with the class's template
arguments.  A call of a static member function whose return type is
still a placeholder converted the member's body under the caller's
template map, so the class's parameters were unbound; the body's queued
entry already records the right map.  GCC 11's std::visit lost
__gen_vtable_impl::__visit_invoke this way ("no match for symbol
'is_same_v'") and with it _S_apply_single_alt.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
decltype(invoke()) in the initializer of a static constexpr member, where
invoke returns a call of a member template with a deduced return type, is
not int while the class is instantiated ("template 'element' not found").
Found while reducing GCC 11's std::visit, which still drops
declval<decltype()>.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [namespace.def.general]/7: a member of an inline namespace can be
explicitly specialized as though it were a member of the enclosing
namespace.  The primary template was looked up in the current scope only,
so `namespace std { template <> struct hash<K> { ... }; }' failed with
libc++, whose std::hash is std::__1::hash ("class template 'hash' not
found", four cbmc-cpp tests on macOS 14).  The lookup now continues into
the inline namespaces, and the specialization is declared in the one that
holds the primary template, so it is found through either name.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [dcl.fct.def.default] and [dcl.fct.def.delete] are C++11 features
that g++ and clang++ also accept in C++98 mode, as an extension with a
warning.  libc++ declares `exception(const exception &) _NOEXCEPT =
default;' in every language mode, so any --cpp98 program that included a
standard header failed to parse on macOS
(cpp98_dynamic_cast_reference_throws_bad_cast).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Each virtual function in an initialized virtual table is an addressed
object, and a candidate target of every call through a function pointer of
its type.  With libc++, <vector> and <functional> declare the locale facets
through <locale> (`extern template class moneypunct_byname<char, false>;'):
their virtual tables alone exceeded the 256 objects of the default
--object-bits 8 (about half of the cbmc-cpp failures on macOS were "too
many addressed objects"), and their destructors were all candidates of
std::function's `__f_->destroy()'.  A class that is never constructed
(N5008 [class.virtual]: its virtual functions cannot be called) needs no
table.

slice_unused_virtual_tables keeps the functions and globals named in code
reachable from the entry point (calls and address-taken functions alike)
and in the initializers of the globals it keeps, and drops the
initialization of the other virtual_table:: symbols.  process_goto_program
runs it before function pointer removal, so that the functions named only
in the dropped tables are no longer address-taken.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
On macOS `gcc' is Apple clang, whose -dumpversion (15 or later) let the
libstdcxxNN-tagged tests run against libc++; with MSVC, the gcc that
Strawberry Perl ships did the same while cbmc preprocessed with cl.  Tests
that check libstdc++ internals (cpp11_libstdcxx_header_drops,
std::__and_, the regex conversion symbol checks) failed in both places.
Such tests get the tag libstdcxx, and the CMake and Makefile exclude it
and the libstdcxxNN tags with MSVC or when the host gcc is clang.
gcc16_optional_transform, a preprocessed libstdc++ 16 file, is gcc-only
like its sibling gcc16_* tests.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Six tests matched the goto program against libstdc++'s spelling of
std::optional's constructors; with libc++ (macOS) they are named
std::__1::optional and the tests failed although the constructors were
there.  The patterns allow the inline namespace.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
A member of the partial specialization fn<R(Args...)> defined outside the
class has no body in fn<int()>, while fn<int(int)> gets it; no error is
reported.  libc++ defines std::function's members this way, so with
libc++ std::function<int()> cannot be called
(cpp11_std_function_call_libcxx, KNOWNBUG libcxx).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…tdcxx

cpp11_stream_setw, cpp11_ostream_integer_insertion_classic_locale,
cpp11_stream_destructor_chain and cpp11_pair_from_function_pointer_rvalue
timed out on macOS 14 (libc++), VS 2022 and VS 2026 (the MSVC STL); with
the CMake profiles' -t not enforced by native Windows perl, the VS 2026
cbmc-cpp shards ran into their 4800 s limit.  They get the libstdcxx tag,
whose description now includes such tests, with the cause in each desc:
libc++ formats integers with vsnprintf, whose library model writes a
nondeterministic string (KNOWNBUG cpp11_ostream_integer_insertion_libcxx),
and std::function<int()> has no call operator with libc++
(cpp11_partial_specialization_member_defined_outside_empty_pack).  The
MSVC STL hangs could not be reduced without its headers.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Strawberry Perl (the VS 2026 CMake job) cannot fork, so test.pl ran tests
without a time limit there and a hanging test held a whole cbmc-cpp shard
until ctest's 4800 s TIMEOUT, without naming it.  With -t, the test now
starts asynchronously (perlport: system(1, LIST) returns the process ID);
test.pl polls it with waitpid(WNOHANG) and, once the time is up, ends the
process tree with taskkill /T /F and reports [TIMEOUT].  MSYS perl (the
make jobs) and other platforms keep their paths.  Not run on Windows
before this commit.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [support.initlist]/1 leaves std::initializer_list's layout to the
implementation.  libstdc++ and libc++ store a begin pointer and a size,
the MSVC STL a begin and an end pointer (`const _Elem *_First, *_Last;').
Building one from a braced-init-list stored the element count into the
second member regardless, and from_integer's precondition failed for a
pointer: 26 cbmc-cpp tests on VS 2022, reproduced from the
msvc-preprocessed-headers artifact.  initializer_list_extent gives the
end pointer for a pointer member.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
The macOS 15 make and VS 2026 jobs uploaded the failed cbmc-cpp tests
preprocessed with the platform's standard library, so that a failure can
be reproduced on Linux (cbmc --os macos, cbmc --winx64).  The steps move
into scripts/preprocess_cpp_tests.sh and .ps1, and the macOS 14 cmake and
VS 2022 make jobs run them too.  Tests that time out on those platforms
are excluded with the libstdcxx tag, or are missing from test.pl's logs
when ctest ends a shard, so env.cpp-preprocess-tests lists them for
preprocessing in addition to the failed ones.

The .ps1 script was run with PowerShell 7 on Linux and a stub cbmc only.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
MSVC provides __builtin_bit_cast(To, from) since VS 2019 16.8, and the
MSVC STL builds _Bit_cast (<type_traits>), and with it std::bit_cast and
<compare>'s orderings, on it.  The scanner recognised it in GCC and clang
modes only, so with MSVC it was an unknown symbol and _Bit_cast lost its
body: 44 such drops among the cbmc-cpp failures on VS 2022, reproduced
from the msvc-preprocessed-headers artifact.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [expr.reinterpret.cast]/1: the result has the type named in the
cast.  conditional_cast leaves the operand alone when the types compare
equal, and irept comparison ignores #c_type, so reinterpret_cast<long *>
of a `long long *' kept the source type and overload resolution, which
distinguishes the two, chose another function or none.  On Windows int and
long have the same width: the MSVC STL's
`__iso_volatile_load32(reinterpret_cast<volatile int *>(&_Uses))' with a
long _Uses found no match, and _Incref_nz (<memory>, 26 drops on VS 2022)
and _Atomic_lock_release (<atomic>, 15) lost their bodies.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
N5008 [dcl.init]/9, [dcl.init.list]/3.11: a mem-initializer `data{}'
value-initializes the array member, zero-initializing every element.  For
an array of arrays the element-wise zeroing assigned an array value to
each element ("direct assignments to arrays not permitted").  The MSVC
STL's _Digit_pair_table (<xutility>, `_Elem _Data[100][2]' with
`: _Data{}') lost its constructor body.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…r pack

N5008 [temp.variadic]/5: a function parameter pack `Args... args' in a
definition stands for any number of parameters.  Matching the out-of-class
definition of a member of `template <class R, class... Args> struct
fn<R(Args...)>' to the instantiated member counted it as one, so for
fn<int()> and fn<int(int, int)> no definition matched and the member had
no body (libc++'s std::function<int()>::operator()).  The definition now
matches any arity from its non-pack parameters on, and the member's
parameters take the names the instantiated body uses for the pack's
elements: args for one, args$0, args$1, ... otherwise.

The KNOWNBUG cpp11_partial_specialization_member_defined_outside_empty_pack
is now CORE as ..._parameter_pack, covering 0, 1 and 2 elements.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant