fix: re-resolve module imports before checking staleness
Adding a data member to a class in a module interface did not rebuild every object compiled against the old layout. The build succeeded with no error or warning and the resulting binary mixed both layouts, surfacing later as a SIGSEGV in a destructor. GetInterfacesAndImplementations scans a TU's `import X;` statements when the source is declared. An import that matches neither a module in the Configuration nor one reachable through `dependencies` was dropped on the floor, leaving that TU with no staleness edge to the interface it consumes. `dependencies` is frequently assigned *after* the scan — AddTest does exactly that, resolving tests/<name>/main.cpp and only then returning a builder whose .Dependencies() supplies the library — so consumers of a dependency's modules routinely carried no edge at all. A layout change then rebuilt the library, relinked the consumer, and kept the consumer's object as it was. Unresolved names are now remembered on the partition/implementation as pendingImports, and Configuration::ResolvePendingImports retries them against the dependency DAG as it stands. Build() calls it immediately before comparing mtimes, which closes the window for every caller rather than only the ones that declare in the right order; TestBuilder::Dependencies also calls it so the Configuration is coherent for anyone inspecting it before the build. Resolves #27
This commit is contained in:
parent
8892154b28
commit
13697cd026
11 changed files with 341 additions and 7 deletions
|
|
@ -0,0 +1,19 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// Copied over lib/Widget.cppm mid-test to stand in for the interface edit from
|
||||
// issue #27: one extra data member, every signature and mangled name unchanged.
|
||||
// Kept as a .cppm.in so the module scanner never treats it as a source of its
|
||||
// own — and so the text does not have to live in a string literal inside the
|
||||
// test, where `export module Widget;` would make the test itself look like an
|
||||
// implementation unit of Widget.
|
||||
|
||||
export module Widget;
|
||||
import std;
|
||||
|
||||
export struct Widget {
|
||||
std::string a;
|
||||
std::string b;
|
||||
};
|
||||
|
||||
export std::size_t WidgetSizeInLibrary();
|
||||
7
tests/IncrementalInterfaceChange/fixture/lib/Widget.cpp
Normal file
7
tests/IncrementalInterfaceChange/fixture/lib/Widget.cpp
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
module Widget;
|
||||
import std;
|
||||
|
||||
std::size_t WidgetSizeInLibrary() { return sizeof(Widget); }
|
||||
19
tests/IncrementalInterfaceChange/fixture/lib/Widget.cppm
Normal file
19
tests/IncrementalInterfaceChange/fixture/lib/Widget.cppm
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Widget;
|
||||
import std;
|
||||
|
||||
// The test rewrites this struct to add a member. Adding one changes the class
|
||||
// layout while leaving every signature and mangled name untouched, so nothing
|
||||
// downstream fails to link — a consumer object left over from before the change
|
||||
// keeps the old sizeof and quietly disagrees with the library.
|
||||
export struct Widget {
|
||||
std::string a;
|
||||
};
|
||||
|
||||
// Deliberately out-of-line, in the library's own implementation unit, so the
|
||||
// value reflects the layout the *library* was compiled against rather than the
|
||||
// caller's. An inline body would be instantiated from the (rebuilt) BMI in the
|
||||
// consumer and would agree with it by construction.
|
||||
export std::size_t WidgetSizeInLibrary();
|
||||
17
tests/IncrementalInterfaceChange/fixture/main.cpp
Normal file
17
tests/IncrementalInterfaceChange/fixture/main.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
import std;
|
||||
import Widget;
|
||||
|
||||
// Prints "<size the consumer was compiled against> <size the library was
|
||||
// compiled against>" and exits 1 when they disagree. A mismatch is the
|
||||
// observable form of the mixed-layout binary a stale consumer object produces —
|
||||
// this reports it instead of waiting for the SIGSEGV that the real-world case
|
||||
// (issue #27) produced in a destructor.
|
||||
int main() {
|
||||
std::size_t here = sizeof(Widget);
|
||||
std::size_t inLibrary = WidgetSizeInLibrary();
|
||||
std::print("{} {}", here, inLibrary);
|
||||
return here == inLibrary ? 0 : 1;
|
||||
}
|
||||
Loading…
Reference in a new issue