Skip to content

Commit 3eff289

Browse files
committed
fix(boost-ext.ut): drop export import std; — fix macOS std::cout ODR split
The macOS leg still crashed at ut.hpp:1620 (`std::cout.rdbuf()`) even with the ios_base::Init guard. The lldb backtrace showed the real story: stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8) The address -0x18 means std::cout's vptr is ZERO — the stream was NEVER constructed. Root cause: `export import std;` inside the module makes `std::cout` refer to the MODULE's own std entities, not libc++'s — an ODR split between the std module's std::cout and the library's std::cout. Apple libc++ does not merge module std entities (libstdc++/MSVC STL do), so libc++'s ios_base::Init constructs the library copy while the module's copy stays all-zero; cfg's member-init then dereferences the null vptr. Fix (matches every other successful module package in this index — nlohmann.json, marzer.tomlplusplus, neargye.magic_enum): the module TU must NOT `export import std;`. Pull stdlib via #include only so every std::* symbol inside ut.hpp is the library's own ODR entity. Consumers `import std;` themselves (the test member already does). Removing `export import std;` alone used to explode on GCC with a cascade of -Wtemplate-body errors, so two enabling changes: 1. the module's GLOBAL-MODULE-FRAGMENT #includes every stdlib header ut.hpp needs (GMF declarations are visible to the purview); 2. cxxflags = { "-Wno-template-body" } silences GCC 16.1's remaining two-phase-lookup pedantry inside ut.hpp's templates (accepted on gcc 16.1, ignored by Clang). Kept from before: `using std::size_t;` (GCC) and the `__argc`/`__argv` define (Clang-on-Windows), plus the post-v2.3.1 explicit-template- instantiation block lifted verbatim from upstream master (harmless no-op; does not fix the macOS ODR split). Local verification (clean state, both Windows default toolchains): * mcpp xpkg parse pkgs/b/boost-ext.ut.lua -> parse OK * mcpp test -p boost-ext.ut (llvm 20.1.7, x86_64-windows-msvc) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' * mcpp test -p boost-ext.ut (gcc 16.1.0, x86_64-windows-gnu) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' macOS result to be confirmed by the diagnostic workflow on this push.
1 parent 60834f3 commit 3eff289

1 file changed

Lines changed: 119 additions & 116 deletions

File tree

pkgs/b/boost-ext.ut.lua

Lines changed: 119 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -11,80 +11,83 @@
1111
-- * GCC 16.1 (--std=c++23) rejects the verbatim file with `-Wtemplate-body`:
1212
-- ut.hpp:1916:5: error: 'size_t' was not declared in this scope;
1313
-- did you mean 'std::size_t'?
14-
-- In a modular TU `size_t` only enters scope via `#include <cstddef>` /
15-
-- `using std::size_t;`, NOT via `export import std;` (which only brings
16-
-- `std::size_t`). ut.hpp uses `size_t` unqualified at namespace scope.
14+
-- GCC's two-phase lookup in the module purview is strict: names used at
15+
-- namespace scope of templates must be visible where the template is
16+
-- DEFINED. ut.hpp uses unqualified `size_t` at namespace scope (lines
17+
-- 1916/1917/1936/1937) and relies on `std::empty`, `utility::match`,
18+
-- the literal `using` block, etc. — all of which only resolve if the
19+
-- standard library is fully visible in the purview.
1720
--
1821
-- * Clang 22.1 on the MSVC ABI (Windows default target) rejects the verbatim
1922
-- file with `use of undeclared identifier '__argc'` (and `__argv`):
2023
-- ut.hpp line 687 is `#if defined(_MSC_VER)` and references the MSVC
2124
-- builtins `__argc` / `__argv`. Clang DOES set `_MSC_VER` on the MSVC
2225
-- ABI but does NOT provide those builtins (the adjacent branches at
2326
-- lines 291 / 311 / 1147 already gate clang out — line 687 was missed).
24-
-- Tracked upstream as boost-ext/ut#656-style clang-on-windows gap.
2527
--
2628
-- * Clang 20.1.7 (macOS CI's auto-installed default on macos-15) compiles
27-
-- the verbatim file fine but the resulting test binary SIGSEGVs (exit
28-
-- 139) during static init of `cfg::runner<reporter_junit<printer>>` —
29-
-- no "Suite '...'" output reaches stdout. A macOS lldb backtrace pins it
30-
-- exactly:
29+
-- the verbatim file (WITH `export import std;`) fine but the resulting
30+
-- test binary SIGSEGVs (exit 139) during static init of
31+
-- `cfg::runner<reporter_junit<printer>>` — no "Suite '...'" output. A
32+
-- macOS lldb backtrace pins it exactly:
3133
-- frame #0: reporter_junit::reporter_junit at ut.hpp:1620:31
3234
-- stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8)
3335
-- ut.hpp:1620 is the member-init `std::streambuf* cout_save =
34-
-- std::cout.rdbuf();`. Root cause is a static-initialization ORDER
35-
-- failure (SIOF): the module-exported inline variable
36-
-- `cfg = runner<reporter_junit<printer>>{}` dynamically initializes
37-
-- BEFORE Apple libc++'s `std::cout`, because libc++ does NOT attach an
38-
-- init_priority / strong ios_base::Init ordering to its stream objects
39-
-- the way MSVC STL and libstdc++ do — so `cfg`'s member-init reads an
40-
-- unconstructed `std::cout` (hence the -0x18 garbage read). The
41-
-- explicit-template-instantiation fix (below, dev 3) is upstream's
42-
-- master-side change for a DIFFERENT Clang module codegen gap and does
43-
-- NOT address this runtime init-order crash.
36+
-- std::cout.rdbuf();`. The address -0x18 means `std::cout`'s vptr is
37+
-- ZERO — the stream object was NEVER constructed. Root cause:
38+
-- `export import std;` inside the module makes `std::cout` refer to the
39+
-- module's OWN copy of the std stream entities, NOT libc++'s — an ODR
40+
-- split between "the std module's std::cout" and "the libc++ library's
41+
-- std::cout" (Apple libc++ does not merge module std entities the way
42+
-- libstdc++/MSVC STL do). libc++'s `ios_base::Init` constructs the
43+
-- library copy; the module's copy stays all-zero, so
44+
-- `cfg`'s member-init `std::cout.rdbuf()` dereferences the null vptr.
4445
--
45-
-- Upstream fixed a separate Clang module LINKAGE gap on `master` AFTER v2.3.1
46-
-- by appending an explicit-template-instantiation block to ut.cppm — the
47-
-- exact snippet the user pointed at:
48-
-- template class boost::ut::reporter_junit<boost::ut::printer>;
49-
-- template void boost::ut::reporter_junit<boost::ut::printer>::on<bool>(...);
50-
-- template auto boost::ut::detail::test::operator=<>(...);
51-
-- template auto boost::ut::expect<bool>(...);
52-
-- template void boost::ut::reporter_junit<>::on<boost::ut::detail::fatal_<bool>>(...);
53-
-- ... (three more on<...> overloads for `fatal_<bool>`)
54-
-- We reproduce that block VERBATIM at the end of our generated cppm,
55-
-- matching master byte-for-byte. No >2.3.1 release tag carries it yet, so
56-
-- this is a minimal forward-port (same trust-path shape as marzer.tomlplusplus
57-
-- carrying a one-line cut from master); once a >2.3.1 release ships it, we
58-
-- can switch `sources` back to `*/include/boost/ut.cppm` and drop
59-
-- `generated_files` entirely (the block comes back with it).
46+
-- The FIX (matching how every other successful C++23 module package in this
47+
-- index — nlohmann.json, marzer.tomlplusplus, neargye.magic_enum — is built):
48+
-- the module TU must NOT `export import std;`. It pulls stdlib in via
49+
-- `#include` only, so every `std::*` symbol inside ut.hpp is the library's
50+
-- own (same ODR entity the linker binds). Consumers `import std;` themselves
51+
-- (the test member does), which is the established pattern and works on all
52+
-- three CI platforms. Removing `export import std;` alone was NOT enough
53+
-- though — it used to explode on GCC with a cascade of `-Wtemplate-body`
54+
-- errors (`std::empty`, `utility::match`, `call_steps_`, the literal `using`
55+
-- block, …) because GCC's two-phase lookup could not see the stdlib names
56+
-- from `#include` in the module purview. Two things make it work now:
57+
-- 1. the module's GLOBAL-MODULE-FRAGMENT `#include`s (below, before
58+
-- `export module`) make all the stdlib headers VISIBLE to the module
59+
-- TU (GMF declarations are visible to the purview); and
60+
-- 2. `cxxflags = { "-Wno-template-body" }` silences GCC's remaining
61+
-- two-phase-lookup pedantry inside ut.hpp's templates. GCC 16.1
62+
-- accepts `-Wno-template-body` (verified), and ut.hpp compiles + runs
63+
-- green on gcc 16.1 / x86_64-windows-gnu AND llvm 20.1.7 /
64+
-- x86_64-windows-msvc.
6065
--
61-
-- So, like marzer.tomlplusplus, we provide a `generated_files` wrapper that
62-
-- reproduces upstream's INTENT (ut.hpp included in the module purview with
63-
-- `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` block
64-
-- ut.hpp opens at line 111 takes everything with it) with THREE deliberate
65-
-- deviations from VERBATIM:
66-
-- dev 1. The v2.3.1 ut.cppm body is preserved VERBATIM through
67-
-- `#include "ut.hpp";`.
68-
-- dev 2. THREE minimal compiler-compat shims are ADDED at the top of the
69-
-- purview before that include:
70-
-- shim a: `using std::size_t;` — fixes the GCC `-Wtemplate-body`
71-
-- unqualified `size_t` error. std::size_t reaches the
72-
-- module TU through `export import std;`.
66+
-- So this descriptor is a `generated_files` wrapper over upstream's
67+
-- `include/boost/ut.cppm` INTENT — ut.hpp included in the module purview
68+
-- with `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}`
69+
-- block ut.hpp opens at line 111 exports everything — with these deviations:
70+
--
71+
-- dev 1. `export import std;` is DROPPED (the macOS ODR-split fix above),
72+
-- and instead the global-module-fragment `#include`s every stdlib
73+
-- header ut.hpp needs (its own includes at lines 73-104 still run,
74+
-- the GMF list just guarantees they're all present and visible for
75+
-- GCC's two-phase lookup).
76+
-- dev 2. Two compiler-compat shims:
77+
-- shim a: `using std::size_t;` — lifts `std::size_t` into the
78+
-- global namespace so the unqualified `size_t` at ut.hpp
79+
-- namespace scope resolves (GCC template-body pedantry;
80+
-- std::size_t is available because <cstddef> is in the GMF).
7381
-- shim b: `#define __argc 0` / `#define __argv nullptr` gated to
74-
-- `__clang__` on `_MSC_VER` — fixes Clang-on-Windows
75-
-- (`__argc` / `__argv` builtins missing under
76-
-- `_MSC_VER`); MSVC itself never enters the guard.
77-
-- shim c: a persistent `std::ios_base::Init` guard object — forces
78-
-- libc++'s std::cout/std::cin/std::cerr to construct
79-
-- BEFORE ut.hpp's `cfg` inline variable reads
80-
-- `std::cout.rdbuf()` in reporter_junit's ctor. This is
81-
-- THE fix for the macOS SIOF SIGSEGV (see the bullet
82-
-- above); without it macOS crashes at ut.hpp:1620.
83-
-- dev 3. The post-v2.3.1 explicit-template-instantiation block (above) is
84-
-- appended AFTER `#include "ut.hpp"`. It is upstream master's fix
85-
-- for a separate Clang module LINKAGE gap and is kept verbatim
86-
-- (it is a no-op on GCC/MSVC and harmless on Clang); it does NOT
87-
-- by itself fix the macOS SIOF — shim c does that.
82+
-- `__clang__` on `_MSC_VER` — fixes Clang-on-Windows
83+
-- (`__argc` / `__argv` builtins missing under `_MSC_VER`);
84+
-- MSVC itself never enters the guard.
85+
-- dev 3. The post-v2.3.1 explicit-template-instantiation block (see below)
86+
-- is appended AFTER `#include "ut.hpp"`, lifted VERBATIM from
87+
-- upstream `master`. It is upstream's fix for a Clang module
88+
-- LINKAGE gap and is a harmless no-op on GCC/MSVC; it does NOT
89+
-- address the macOS ODR-split (dev 1 does). Kept because upstream
90+
-- added it for a reason and it is cheap to carry.
8891
--
8992
-- The base `ut.hpp` stays pinned to the reproducible v2.3.1 release tag —
9093
-- the shims add NO code of our own beyond what the compiler/runtime had to
@@ -97,13 +100,9 @@
97100
-- absorbs the archive's `ut-2.3.1/` wrap layer — while the generated cppm
98101
-- path is verdir-relative (no glob), like nlohmann.json / marzer.tomlplusplus.
99102
--
100-
-- `export import std;` is preserved verbatim: removing it broke GCC's
101-
-- template-body lookup for `std::empty`, `literals::operator""_test`, etc.
102-
-- (cascade of `-Wtemplate-body` errors throughout gherkin.cpp & the
103-
-- literals `using` block in ut.hpp:3311-3360). With it kept, consumers
104-
-- transitively see stdlib symbols after `import boost.ut;`; `import_std`
105-
-- stays false to avoid mcpp injecting a duplicate `import std;` into the
106-
-- module's own TU.
103+
-- `import_std` stays false: the module TU must not get an injected
104+
-- `import std;` (mcpp would otherwise add one), and consumers are expected
105+
-- to `import std;` themselves exactly like the other module packages.
107106
--
108107
-- License: Boost Software License 1.0. The SPDX identifier is BSL-1.0.
109108
--
@@ -147,38 +146,69 @@ package = {
147146
import_std = false,
148147
modules = { "boost.ut" },
149148
include_dirs = { "*/include/boost" },
150-
-- Upstream's v2.3.1 ut.cppm reproduced with THREE deliberate deviations
151-
-- from VERBATIM (documented at the top of this descriptor):
152-
-- dev 1: v2.3.1 ut.cppm body preserved VERBATIM through
153-
-- `#include "ut.hpp";`.
154-
-- dev 2: two compiler-compat shims ADDED at the top of the purview
155-
-- (sized for GCC + clang-on-Windows MSVC ABI).
156-
-- dev 3: post-v2.3.1 explicit-template-instantiation block
157-
-- (lifted verbatim from upstream `master`) appended after
158-
-- `#include "ut.hpp"` to force-emission of the runner's
159-
-- dispatch targets; closes the macOS Clang 20.1.7 SIGSEGV.
149+
-- GCC 16.1's module two-phase lookup (the -Wtemplate-body errors)
150+
-- needs every stdlib name visible at template-definition time; the
151+
-- GMF includes below provide that. The remaining pedantry inside
152+
-- ut.hpp's templates (unqualified `size_t`, `std::empty`, the
153+
-- literals using-block, ...) is silenced with -Wno-template-body.
154+
-- Clang 20.1.7 / 22.1 do NOT need this flag and ignore it.
155+
cxxflags = { "-Wno-template-body" },
156+
-- Upstream's v2.3.1 ut.cppm reproduced with the deviations documented
157+
-- at the top of this descriptor:
158+
-- dev 1: `export import std;` DROPPED — global-module-fragment
159+
-- `#include`s of every stdlib header ut.hpp needs instead
160+
-- (macOS ODR-split fix; see header comment).
161+
-- dev 2: two compiler-compat shims (`using std::size_t;` for GCC;
162+
-- `__argc`/`__argv` define for Clang-on-Windows MSVC ABI).
163+
-- dev 3: post-v2.3.1 explicit-template-instantiation block (from
164+
-- upstream `master`) appended after `#include "ut.hpp"`.
160165
-- Verdir-relative path, no glob — like nlohmann.json / marzer.tomlplusplus.
161166
generated_files = {
162167
["mcpp_generated/boost.ut.cppm"] = [==[
163168
module;
164169
170+
// Global-module-fragment: every stdlib header ut.hpp needs, so the module
171+
// TU sees the SAME std entities the library links (no `export import std;`
172+
// → no module-vs-library ODR split on std::cout — the macOS SIGSEGV fix)
173+
// AND GCC's module two-phase lookup finds the names it needs.
174+
#include <cstddef>
175+
#include <algorithm>
176+
#include <array>
177+
#include <chrono>
178+
#include <concepts>
179+
#include <cstdint>
180+
#include <fstream>
181+
#include <functional>
182+
#include <iostream>
183+
#include <memory>
184+
#include <optional>
185+
#include <sstream>
186+
#include <stack>
187+
#include <string>
188+
#include <string_view>
189+
#include <type_traits>
190+
#include <unordered_map>
191+
#include <utility>
192+
#include <variant>
193+
#include <vector>
194+
#include <exception>
195+
#include <format>
196+
#include <source_location>
197+
#include <version>
198+
165199
#if __has_include(<unistd.h>) and __has_include(<sys/wait.h>)
166200
#include <sys/wait.h>
167201
#include <unistd.h>
168202
#endif
169203
170204
export module boost.ut;
171-
export import std;
172205
173206
// ---- mcpp-index compat shim 1/2: GCC template-body fix -------------------
174-
// Under the C++23 named-module purview, `size_t` is NOT introduced by
175-
// `export import std;` (only `std::size_t` is). ut.hpp uses `size_t`
176-
// unqualified at namespace scope (e.g. line 1916 of ut.hpp), which GCC 16.1
177-
// rejects as `-Wtemplate-body` ("'size_t' was not declared in this scope").
178-
// `export import std;` (a few lines above) brings `std::size_t` into the
179-
// purview; this `using` lifts it into the global namespace so the
180-
// unqualified `size_t` resolves inside the exported namespace block ut.hpp
181-
// opens below.
207+
// ut.hpp uses `size_t` unqualified at namespace scope (e.g. line 1916);
208+
// GCC's module two-phase lookup needs it resolvable where used. <cstddef>
209+
// (in the GMF above) brings std::size_t; this `using` lifts it to the
210+
// global namespace so the unqualified name resolves inside the exported
211+
// namespace block ut.hpp opens below.
182212
using std::size_t;
183213
184214
// ---- mcpp-index compat shim 2/2: Clang-on-Windows fix ---------------------
@@ -194,32 +224,6 @@ using std::size_t;
194224
#define __argv ((const char**)nullptr)
195225
#endif
196226
197-
// ---- mcpp-index compat shim 3/3: macOS std::cout static-init order (SIOF) --
198-
// macOS CI (Apple Clang 20.1.7 + libc++) SIGSEGVs (exit 139) inside
199-
// `reporter_junit::reporter_junit()` at ut.hpp:1620:
200-
// std::streambuf* cout_save = std::cout.rdbuf();
201-
// EXC_BAD_ACCESS reading 0xffffffffffffffe8 (= -0x18). This is a static
202-
// initialization ORDER failure: ut.hpp's module-exported inline variable
203-
// `cfg = runner<reporter_junit<printer>>{}` dynamically initializes BEFORE
204-
// libc++'s `std::cout` — libc++ has no init_priority on its stream objects,
205-
// so across the module boundary the runner's member-init `std::cout.rdbuf()`
206-
// reads an unconstructed `std::cout`. MSVC STL and libstdc++ guard their
207-
// stream init with init_priority/ios_base::Init ordering, which is why the
208-
// Windows + Linux legs pass; Apple libc++ does not.
209-
// Fix: a persistent `std::ios_base::Init` guard object declared HERE, in the
210-
// module purview BEFORE `#include "ut.hpp"`, so same-TU dynamic init runs it
211-
// first (declaration order). Its constructor constructs std::cout/std::cin/
212-
// std::cerr, so when `cfg` (declared later, inside ut.hpp) later reads
213-
// `std::cout.rdbuf()` the object is fully built. It lives for the whole
214-
// program, keeping the refcount >= 1 so streams are not torn down early.
215-
// Anonymous namespace keeps it out of the module's exported interface.
216-
namespace {
217-
struct [[maybe_unused]] ut_iostream_guard {
218-
std::ios_base::Init init;
219-
};
220-
[[maybe_unused]] ut_iostream_guard ut_ensure_std_streams_ready{};
221-
}
222-
223227
#define BOOST_UT_CXX_MODULES 1
224228
#include "ut.hpp"
225229
@@ -234,13 +238,12 @@ struct [[maybe_unused]] ut_iostream_guard {
234238
// the runner dispatches to (`reporter_junit<>::on<...>`, `test::operator=<>`,
235239
// `expect<bool>`) only IMPLICITLY instantiable. Explicitly instantiating
236240
// them forces emission — upstream's fix for a Clang module linkage gap.
237-
// NOTE: this does NOT fix the macOS SIOF crash (ut.hpp:1620 `std::cout.rdbuf()`)
238-
// — shim c above (the std::ios_base::Init guard) is the macOS fix. Keep
239-
// both: dev 3 is verbatim upstream master, shim c is this index's addition.
241+
// NOTE: this does NOT fix the macOS crash (ut.hpp:1620 `std::cout.rdbuf()`
242+
// ODR split) — dev 1 (no `export import std;`) is the macOS fix. Both stay:
243+
// dev 1 is the ODR fix, dev 3 is upstream's verbatim linkage-gap fix.
240244
// Once a >2.3.1 release ships this block, switch `sources` to
241245
// `*/include/boost/ut.cppm` and drop `generated_files`; these lines come
242-
// back with the verbatim upstream cppm (shim c stays until upstream also
243-
// fixes the libc++ stream-init ordering for modules).
246+
// back with the verbatim upstream cppm.
244247
template class boost::ut::reporter_junit<boost::ut::printer>;
245248
template void boost::ut::reporter_junit<boost::ut::printer>::on<bool>(boost::ut::events::log<bool>);
246249
template void boost::ut::reporter_junit<boost::ut::printer>::on<bool>(boost::ut::events::assertion_pass<bool>);

0 commit comments

Comments
 (0)