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 | browse on GitHub |
Category: Symbol ABI | Verdict: BREAKING
What breaks¶
Functions are moved from inline namespace v1 to inline namespace v2. While
source code using crypto::encrypt() compiles fine against both versions (inline
namespaces are transparent to unqualified lookup), the mangled symbol names
encode the inline namespace:
- v1 symbol:
_ZN6crypto2v17encryptEPKNS0_7ContextEPKci(crypto::v1::encrypt) - v2 symbol:
_ZN6crypto2v27encryptEPKNS0_7ContextEPKci(crypto::v2::encrypt)
Old binaries request the v1-mangled name from the dynamic linker. Since v2 only exports the v2-mangled name, the symbols are not found at load time.
This is the mechanism libstdc++ uses for ABI versioning (e.g., std::__cxx11::string
vs std::string), and it's the exact break that caused widespread ecosystem issues
during the GCC 5.x transition.
Why abicheck catches it¶
Symbol comparison detects that old mangled names disappeared and new ones with
different namespace encoding appeared (inline_namespace_moved). This is flagged
as BREAKING rather than simple removal + addition, because the pattern indicates
an intentional version bump.
Code diff¶
// v1.h
namespace crypto {
inline namespace v1 {
int encrypt(const Context *ctx, const char *data, int len);
}}
// v2.h — inline namespace version bumped
namespace crypto {
inline namespace v2 {
int encrypt(const Context *ctx, const char *data, int len);
}}
Real Failure Demo¶
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
# Build v2 (namespace v2)
g++ -shared -fPIC -g v2.cpp -o libcrypto_ex.so
# Verify v2 exports v2-namespace symbols
nm -D libcrypto_ex.so | grep encrypt
# -> T _ZN6crypto2v27encryptEPKNS0_7ContextEPKci
./app
# -> ./app: symbol lookup error: undefined symbol: _ZN6crypto2v17encryptE...
Why CRITICAL: The mangled symbol name includes the inline namespace version.
Old binaries request crypto::v1::encrypt but only crypto::v2::encrypt exists.
The dynamic linker fails immediately.
How to fix¶
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; // or provide a wrapper
}
}
Or use ELF symbol versioning to map old symbols to new implementations.
Real-world example¶
The most famous instance is the libstdc++ dual ABI introduced in GCC 5. The
std::string implementation changed, and the new one lives in
std::__cxx11::basic_string. The _GLIBCXX_USE_CXX11_ABI macro controls which
inline namespace is active, and mixing old and new binaries produces exactly this
kind of "undefined symbol" failure. This caused years of ecosystem pain for Linux
distributions.
References¶
- Itanium C++ ABI: Mangling of inline namespaces
- GCC 5 Changes: Dual ABI
- Inline Namespaces 101 (foonathan)
Source files¶
See also: Examples overview · All BREAKING cases · Category: Breaking.