Tests are relinked but not recompiled when a dependency's module interface changes, producing ABI-mismatch crashes #26

Closed
opened 2026-07-27 01:03:18 +00:00 by catbot · 0 comments
Member

What happens

When a module interface (.cppm) in a library configuration changes in a way that alters a type's layout, crafter-build test rebuilds the library and relinks the test executable, but does not recompile the test's main.cpp. The test's object file still encodes the old layout, so the resulting binary has an ABI mismatch with the library it links against and crashes at runtime — SIGSEGV or SIGABRT — with no build error and nothing in the output to suggest anything is wrong.

Reproduction

In Catcrafts/Crafter.Network at a79ab6a, add a member to an exported class that a test constructs on the stack:

 // interfaces/Crafter.Network-ClientHTTP1.cppm
         std::chrono::milliseconds timeout{30000};
+        std::array<char, 4096> experimentPadding{};

tests/ShouldSendRecieveHTTP1/main.cpp has ClientHTTP1 client("localhost", 8090); — a stack object whose size just changed. Then:

$ crafter-build test 'ShouldSendRecieveHTTP1'
[9/10] Compiling interface Crafter.Network
[10/10] Compiling Crafter.Network-ClientHTTP1.cpp
[11/11] Linking crafter-network
[12/12] Linking ShouldSendRecieveHTTP1
💥 ShouldSendRecieveHTTP1 (30134ms) crashed: SIGABRT

Note there is no Compiling main.cpp step: the library was rebuilt, the test was relinked, and the stale main.o was reused. The library's constructor writes 4 KiB past what the caller reserved, so the test's stack is corrupted.

rm -rf build bin && crafter-build test passes cleanly, which confirms the sources are fine and only the incremental path is affected.

Why it is worth fixing

The failure mode is unkind: the crash happens far from the cause, it looks like a bug in the code under test, and it stays reproducible until someone thinks to wipe the build directory. On Crafter.Network the first symptom was three unrelated-looking tests failing at once — including a byte-comparison failure in a large-body transfer — immediately after a one-line change that could not have caused any of them.

It also means crafter-build test cannot be trusted right after an interface edit, which is exactly when a test result matters most.

Expected

A test's translation units should be recompiled when any module interface they transitively import changes — the same dependency edge that already correctly triggers the relink, and that already correctly recompiles the library's own implementation files.

## What happens When a module interface (`.cppm`) in a library configuration changes in a way that alters a type's layout, `crafter-build test` rebuilds the library and **relinks** the test executable, but does not **recompile** the test's `main.cpp`. The test's object file still encodes the old layout, so the resulting binary has an ABI mismatch with the library it links against and crashes at runtime — SIGSEGV or SIGABRT — with no build error and nothing in the output to suggest anything is wrong. ## Reproduction In `Catcrafts/Crafter.Network` at `a79ab6a`, add a member to an exported class that a test constructs on the stack: ```diff // interfaces/Crafter.Network-ClientHTTP1.cppm std::chrono::milliseconds timeout{30000}; + std::array<char, 4096> experimentPadding{}; ``` `tests/ShouldSendRecieveHTTP1/main.cpp` has `ClientHTTP1 client("localhost", 8090);` — a stack object whose size just changed. Then: ``` $ crafter-build test 'ShouldSendRecieveHTTP1' [9/10] Compiling interface Crafter.Network [10/10] Compiling Crafter.Network-ClientHTTP1.cpp [11/11] Linking crafter-network [12/12] Linking ShouldSendRecieveHTTP1 💥 ShouldSendRecieveHTTP1 (30134ms) crashed: SIGABRT ``` Note there is no `Compiling main.cpp` step: the library was rebuilt, the test was relinked, and the stale `main.o` was reused. The library's constructor writes 4 KiB past what the caller reserved, so the test's stack is corrupted. `rm -rf build bin && crafter-build test` passes cleanly, which confirms the sources are fine and only the incremental path is affected. ## Why it is worth fixing The failure mode is unkind: the crash happens far from the cause, it looks like a bug in the code under test, and it stays reproducible until someone thinks to wipe the build directory. On Crafter.Network the first symptom was three unrelated-looking tests failing at once — including a byte-comparison failure in a large-body transfer — immediately after a one-line change that could not have caused any of them. It also means `crafter-build test` cannot be trusted right after an interface edit, which is exactly when a test result matters most. ## Expected A test's translation units should be recompiled when any module interface they transitively import changes — the same dependency edge that already correctly triggers the relink, and that already correctly recompiles the library's own implementation files.
catbot 2026-07-30 18:20:43 +00:00
Sign in to join this conversation.
No description provided.