Case 16 β Inline β Non-inline (ODR / Symbol Appearance)¶
| Field | Value |
|---|---|
| Verdict | π’ COMPATIBLE |
| Category | Addition (Compatible) |
| Platforms | Linux |
| Flags | β |
Detected ChangeKinds |
β |
| Source files | browse on GitHub |
Verdict: π’ COMPATIBLE
What changes¶
| Version | Where is fast_hash? |
|---|---|
| v1 | Header-only inline β callers have their own copy |
| v2 | Moved to .so β now an exported symbol |
What breaks at binary level¶
Scenario A β Stale callers (compiled with v1 header):
The caller has fast_hash inlined. The v2 .so also has fast_hash. At link time,
the linker sees two definitions β caller's inlined copy and the .so export. Normally
the inline version "wins" locally. But if the implementation diverges between v1
(inlined) and v2 (in .so), results differ. This is an ODR violation.
Scenario B β Fresh callers (compiled with v2 header, linked against v1 .so):
The caller expects fast_hash as an imported symbol. But v1 .so has no
fast_hash symbol at all. Link fails with "undefined symbol: fast_hash".
In both scenarios the breakage is subtle and depends on build order.
Why abidiff misses it¶
abidiff compares two .so files. v1 .so has no fast_hash symbol (it was
inline). v2 .so adds fast_hash. abidiff reports this as a new export, not a
breaking change. It cannot know that callers were compiled with the old inline version.
Why ABICC catches it¶
ABICC parses both header ASTs. It sees fast_hash was inline in v1 and non-inline
in v2. This is a semantic change: the inline assumption is gone. ABICC flags:
"Function 'fast_hash' changed: inline removed".
Real-world example¶
In abseil-cpp, several string utility functions were moved from headers into the
.so during the monorepo refactor (2021). Users who pinned to old .so files but
updated their headers got linker errors. Some projects shipped both a header-inline
and a .so symbol β causing ODR violations with LTO builds.
Code diff¶
-// v1.hpp
-inline int fast_hash(int x) {
- return static_cast<int>(static_cast<unsigned>(x) * 2654435761U);
-}
+// v2.hpp
+int fast_hash(int x); // declaration only
+// v2.cpp
+int fast_hash(int x) { // now in .so
+ return static_cast<int>(static_cast<unsigned>(x) * 2654435761U);
+}
Reproduce steps¶
cd examples/case16_inline_to_non_inline
# Build .so files
g++ -shared -fPIC -std=c++17 -g v1.cpp -o libv1.so
g++ -shared -fPIC -std=c++17 -g v2.cpp -o libv2.so
# Check symbol table
nm --dynamic libv1.so | grep fast_hash || echo "v1: no fast_hash symbol (expected)"
nm --dynamic libv2.so | grep fast_hash # v2: symbol present
# abidiff: shows fast_hash as NEW addition (not a break)
abidw --out-file v1.xml libv1.so
abidw --out-file v2.xml libv2.so
abidiff v1.xml v2.xml || true
# ABICC: catches inlineβnon-inline semantic change
abi-compliance-checker -lib fast_hash -v1 1.0 -v2 2.0 \
-header v1.hpp -header v2.hpp
Real Failure Demo¶
Severity: CRITICAL
Scenario B β linker error: compile app with v2.hpp (no inline), link against v1.so (no symbol).
# Build v1.so (fast_hash is inline β NOT in .so)
g++ -shared -fPIC -std=c++17 -g v1.cpp -o libhash.so
# Compile app with v2.hpp (declaration only, expects symbol in .so)
g++ -std=c++17 -g app.cpp -I. -L. -lhash -Wl,-rpath,. -o app
# β /usr/bin/ld: app.cpp:(.text+0x...): undefined reference to 'fast_hash(int)'
# β collect2: error: ld returned 1 exit status
# Only works when linking against v2.so (has the symbol)
g++ -shared -fPIC -std=c++17 -g v2.cpp -o libhash.so
g++ -std=c++17 -g app.cpp -I. -L. -lhash -Wl,-rpath,. -o app # links OK
./app
# β fast_hash(42) = ...
Why CRITICAL: Existing binaries compiled against v1 (inline) are unaffected β they have the inline body baked in. The break hits new consumers: any code compiled against v2.hpp (declaration only) that links against v1.so gets a hard linker error because the symbol doesn't exist in v1.so. This forces a coordinated upgrade: v2.hpp and v2.so must ship together.
Why runtime result may differ from verdict¶
Inlineβnon-inline: old binary uses inlined copy, runtime unaffected
References¶
Source files¶
See also: Examples overview Β· All COMPATIBLE cases Β· Category: Addition (Compatible).