Case 101: Inline Namespace Version Bumped¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS |
| Flags | ABI break |
Detected ChangeKinds |
inline_namespace_version_bumped |
| Source files | examples/case101_inline_namespace_version_bumped/ |
Category: Symbol API | Verdict: โ BREAKING
Verdict and consumer impact¶
The library publishes its API under a versioned inline namespace _V1.
v2 bumps the segment to _V2. Consumers spell lib::sort()/lib::unique()
in both versions โ source keeps compiling unchanged โ but every mangled
symbol changes (_ZN3lib3_V14sortEv โ _ZN3lib3_V24sortEv). Any program
that mixes an old TU (or a pre-built .so) with a newly-compiled TU
against v2's headers ODR-violates: the two "same-named" functions are, at
the ABI level, entirely different symbols.
Old/new diff¶
| v1.h | v2.h |
|---|---|
namespace lib { inline namespace _V1 { void sort(); void unique(); } } |
namespace lib { inline namespace _V2 { void sort(); void unique(); } } |
abicheck command¶
clang++ -std=c++17 -shared -fPIC -g v1.cpp -o liblib_v1.so
clang++ -std=c++17 -shared -fPIC -g v2.cpp -o liblib_v2.so
abicheck compare liblib_v1.so liblib_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
## Breaking Changes
- func_removed: Public function removed: lib::_V1::sort
- func_removed: Public function removed: lib::_V1::unique
- inline_namespace_version_bumped: Inline namespace version bumped:
'lib::_V1::sort' -> 'lib::_V2::sort' (version segment changed from
[1] to [2]); mangled names change so old and new TUs of the same
program ODR-violate.
- inline_namespace_version_bumped: Inline namespace version bumped:
'lib::_V1::unique' -> 'lib::_V2::unique' (same pattern)
## Additions
- func_added: New public function: lib::_V2::sort
- func_added: New public function: lib::_V2::unique
Minimum evidence¶
min_evidence: L0 โ the mangled symbols disappearing from _V1:: and
reappearing under _V2:: are enough on their own to reach the BREAKING
verdict, even on a fully stripped binary with no debug info at all (as
plain func_removed_elf_only/func_added pairs). The specific
inline_namespace_version_bumped diagnosis needs the declared/qualified
names, which abicheck recovers from DWARF (L1) or header text (L2) โ no
public headers are required as long as DWARF is present.
INLINE_NAMESPACE_VERSION_BUMPED is a specialization of the existing
symbol-table-driven inline_namespace_moved detector that instead fires
from declared-name evidence, so it also works on header-only inputs and
single-symbol bumps where the older detector (which requires โฅ2 correlated
mangled-symbol moves) would miss it โ not exercised by this fixture, which
ships a real .so either way.
Why abicheck catches it¶
Each function's qualified declared name (recovered from DWARF, or from a
header AST when present) includes its enclosing inline-namespace segment.
The detector matches functions by leaf name across snapshots and checks
whether the immediately-enclosing namespace segment looks like a version
tag (_V<N>) that incremented โ when it did, it reports
inline_namespace_version_bumped alongside the underlying func_removed/
func_added pair the mangled-name change produces.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: compile app against v1, swap in v2 .so without recompile.
# Build old library + app
ln -sf liblib_v1.so liblib.so
clang++ -std=c++17 app.cpp -L. -llib -Wl,-rpath,. -o app
./app
# โ exits 0 (lib::sort(); lib::unique(); both resolve to _V1 symbols)
# Swap in new library (no recompile)
ln -sf liblib_v2.so liblib.so
./app
# โ ./app: symbol lookup error: ./app: undefined symbol: _ZN3lib3_V14sortEv
Why CRITICAL: the old binary's call sites are hard-linked against the
_V1-mangled symbols; v2's .so only exports _V2-mangled equivalents,
so the dynamic linker cannot resolve them and the process fails to start.
Safe redesign¶
Don't bump a versioned inline namespace's segment without a SONAME bump โ treat it exactly like any other ABI-breaking symbol rename, because that's what it mechanically is. If both API generations must coexist in one build (e.g. during a migration window), keep both inline namespaces present simultaneously rather than replacing one with the other, or use a non-inline (explicitly qualified) versioned namespace so consumers opt in deliberately.
Real-world example: libc++ and other standard-library implementations
use versioned inline namespaces (std::__1::, std::__2::) for exactly
this reason โ bumping the version segment is treated as equivalent to an
SONAME-level ABI break, never done silently within a compatible release.
Cross-tool comparison¶
abidiff (binary-only) would see the same symbol churn and report a
function removal/addition pair per symbol; it has no dedicated concept of
"inline namespace version bump" โ that framing, and the single detector
tying the removal/addition pair together as one semantic event, is unique
to abicheck. Not re-verified numerically in this environment (abidiff is
not installed here).
References¶
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.