Case 71: Inline Namespace Moved¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS, Windows |
| Flags | ABI break |
Detected ChangeKinds |
inline_namespace_moved |
| Source files | examples/case71_inline_namespace_moved/ |
Category: Symbol ABI | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
crypto::encrypt()/decrypt() move from inline namespace v1 to
inline namespace v2. Source using the unqualified crypto::encrypt()
name compiles fine against either version โ inline namespaces are
transparent to unqualified lookup โ but the mangled symbol name encodes
the inline namespace (crypto::v1::encrypt vs crypto::v2::encrypt). Old
binaries request the v1-mangled symbol; v2 only exports the v2-mangled one,
so the dynamic linker fails to resolve it at load time.
Old/new diff¶
| v1.h | v2.h |
|---|---|
namespace crypto { inline namespace v1 { ... } } |
namespace crypto { inline namespace v2 { ... } } |
_ZN6crypto2v17encryptEPKNS0_7ContextEPKci |
_ZN6crypto2v27encryptEPKNS0_7ContextEPKci |
abicheck command¶
g++ -shared -fPIC -g v1.cpp -o libfoo_v1.so
g++ -shared -fPIC -g v2.cpp -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- type_removed: Type removed: crypto::v1::Context
> Old code references a type that no longer exists; compilation or link failure.
- func_removed_elf_only: Elf_only function removed:
crypto::v1::decrypt(crypto::v1::Context const*, char const*, int)
- func_removed_elf_only: Elf_only function removed:
crypto::v1::encrypt(crypto::v1::Context const*, char const*, int)
> Exported function symbol removed from the binary; old binaries that link
or dlsym() it can fail even without header evidence.
- inline_namespace_moved: Inline namespace move detected: 2 symbols appear to
have moved between inline namespace versions (e.g. ::v1:: -> ::v2::);
mangled names changed
- inline_namespace_version_bumped: Inline namespace version bumped:
'crypto::v1::Context' -> 'crypto::v2::Context' (version segment changed
from [1] to [2]); mangled names change so old and new TUs of the same
program ODR-violate.
Additions:
- func_added: New public function: crypto::v2::decrypt(...), crypto::v2::encrypt(...)
- type_added: New type: crypto::v2::Context
Minimum evidence¶
min_evidence: L0 โ the exported-symbol table alone is enough: the
mangled names for crypto::v1::* disappear and crypto::v2::* names with
matching demangled signatures appear in their place, which is exactly the
pattern abicheck's inline-namespace-move detector matches on. -g above is
only there so the Runtime failure demonstration below can build a
matching app.
Why abicheck catches it¶
abicheck demangles each exported symbol and compares the demangled
signature (ignoring the inline-namespace segment) across versions; when it
finds the same signature reappearing under a different numbered inline
namespace, it reports inline_namespace_moved โ a mangled-name-only
signal, no debug info or headers required.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: compile app against v1, swap in v2 .so without recompile.
# Build v1 and app
g++ -shared -fPIC -g v1.cpp -o libcrypto_ex.so
g++ -g app.cpp -L. -lcrypto_ex -Wl,-rpath,. -o app
./app
# โ encrypt() = 262
# โ Expected: 262
# Verify v1 exports v1-namespace symbols
nm -D libcrypto_ex.so | grep encrypt
# โ T _ZN6crypto2v17encryptEPKNS0_7ContextEPKci
# Swap in v2 (namespace v2, no recompile)
g++ -shared -fPIC -g v2.cpp -o libcrypto_ex.so
nm -D libcrypto_ex.so | grep encrypt
# โ T _ZN6crypto2v27encryptEPKNS0_7ContextEPKci
./app
# โ ./app: symbol lookup error: ./app: undefined symbol:
# _ZN6crypto2v17encryptEPKNS0_7ContextEPKci
Why CRITICAL: the mangled symbol name includes the inline namespace
version. Old binaries request crypto::v1::encrypt but only
crypto::v2::encrypt exists in the swapped-in library, so the dynamic
linker fails immediately instead of running with wrong data.
Safe redesign¶
Keep the old inline namespace alongside the new one with compatibility aliases:
namespace crypto {
inline namespace v2 {
int encrypt(const Context *ctx, const char *data, int len);
}
// Backward compatibility
namespace v1 {
using v2::encrypt;
}
}
Real-world example: the libstdc++ dual ABI introduced in GCC 5 uses
exactly this mechanism โ std::string moved to
std::__cxx11::basic_string, and mixing binaries built with different
_GLIBCXX_USE_CXX11_ABI settings produces this same "undefined symbol"
failure, causing years of ecosystem pain for Linux distributions.
Cross-tool comparison¶
References¶
- Itanium C++ ABI: Mangling of inline namespaces
- GCC 5 Changes: Dual ABI
- Inline Namespaces 101 (foonathan)
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.