Skip to content

Commit f207cac

Browse files
committed
fix(build): test binaries use the same static -load_hidden libc++ as distributables
Forensics rounds 5-6 closed the dynamic route: this llvm distribution's libc++abi/libunwind dylibs upward-link /usr/lib/libc++, so the system libc++ ALWAYS loads beside the toolchain's — gtest's static initializers then construct a stringstream in one copy and destroy it in the other (BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED in locale::~locale). Static hidden archives keep exactly one libc++ inside the binary — the shape mcpp's own shipped binaries have used green for months. If the historical gtest exit-teardown SIGABRT resurfaces it will now be caught by this same CI lane with forensics in place.
1 parent 4f28e6f commit f207cac

1 file changed

Lines changed: 19 additions & 26 deletions

File tree

src/build/flags.cppm

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -386,35 +386,28 @@ CompileFlags compute_flags(const BuildPlan& plan) {
386386
+ " -Wl,-load_hidden," + escape_path(libcxxAbiA);
387387
}
388388
}
389-
// TestBinary: link the toolchain's OWN libc++, dynamically. Tests
390-
// previously took the SYSTEM -lc++ while compiling against the
389+
// TestBinary: SAME static -load_hidden libc++ as distributables.
390+
// Tests previously took the SYSTEM -lc++ while compiling against the
391391
// toolchain's libc++ HEADERS — a header/dylib version split that
392392
// detonated when libc++ 22 moved string hashing out of line
393-
// (undefined __hash_memory against Apple's older dylib, 2026-07-08).
394-
// Tests are host-only by definition, so an rpath into the toolchain
395-
// registry is fine here in a way it isn't for distributables:
396-
// same-version headers and dylib by construction, and dynamic
397-
// teardown avoids the static-destruction SIGABRT that motivated the
398-
// system-lib exception in the first place (gtest's main has no _Exit
399-
// guard). Falls back to the system -lc++ when the toolchain ships no
400-
// dylib. Design: .agents/docs/2026-07-08-root-cause-remediation-design.md A1.
393+
// (undefined __hash_memory, 2026-07-08). The dynamic alternative
394+
// (toolchain libc++.dylib + rpath) is a dead end with this
395+
// distribution: its abi/unwind dylibs upward-link /usr/lib/libc++,
396+
// so the SYSTEM libc++ still loads next to the toolchain's and
397+
// gtest's initializers freed across the two copies
398+
// (BUG_IN_CLIENT_OF_LIBMALLOC, CI crash forensics rounds 5-6).
399+
// Static hidden archives keep exactly ONE libc++, inside the
400+
// binary — the same already-proven shape mcpp/xlings ship with.
401+
// Design: .agents/docs/2026-07-08-root-cause-remediation-design.md A1.
401402
if (!llvmRootForStdlib.empty()) {
402-
auto libDir = llvmRootForStdlib / "lib";
403-
if (std::filesystem::exists(libDir / "libc++.dylib")
404-
|| std::filesystem::exists(libDir / "libc++.1.dylib")) {
405-
// Link the dylibs BY PATH, not -l: with libc++.a and
406-
// libc++.dylib side by side in libDir, -l resolution pulled
407-
// part of the ARCHIVE into the binary alongside the dylib —
408-
// two libc++ global states in one process, and every exit
409-
// aborted in locale::~locale with
410-
// BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED (CI crash
411-
// report, round 5). A concrete dylib file path has no
412-
// search ambiguity. -lc++abi stays (this distribution's
413-
// libc++ does not reexport the abi — round 2).
414-
f.ldStdlibTest = " -nostdlib++ "
415-
+ escape_path(libDir / "libc++.1.dylib")
416-
+ " " + escape_path(libDir / "libc++abi.1.dylib")
417-
+ " -Wl,-rpath," + escape_path(libDir);
403+
auto libDir = llvmRootForStdlib / "lib";
404+
auto libcxxA = libDir / "libc++.a";
405+
auto libcxxAbiA = libDir / "libc++abi.a";
406+
if (std::filesystem::exists(libcxxA)
407+
&& std::filesystem::exists(libcxxAbiA)) {
408+
f.ldStdlibTest = " -nostdlib++"
409+
" -Wl,-load_hidden," + escape_path(libcxxA)
410+
+ " -Wl,-load_hidden," + escape_path(libcxxAbiA);
418411
}
419412
}
420413
std::string version_min;

0 commit comments

Comments
 (0)