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 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

abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml

References


Source files

  • CMakeLists.txt
  • app.cpp
  • v1.cpp
  • v1.h
  • v2.cpp
  • v2.h

See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.