Skip to content

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


Source files

See also: Examples overview · All BREAKING cases · Category: Breaking.