Skip to content

Case 47: Inline Method Moved Out-of-Line

Field Value
Verdict ๐ŸŸข COMPATIBLE
Category Addition (Compatible)
Platforms Linux, macOS, Windows
Flags โ€”
Detected ChangeKinds func_added
Source files examples/case47_inline_to_outlined/

Category: Compatible | Verdict: ๐ŸŸข COMPATIBLE

Verdict and consumer impact

Calculator::add() is defined inline in v1's header, so it has no exported symbol โ€” every consumer gets its own inlined copy baked into their binary at compile time. In v2, add() is moved out-of-line into the .cpp file, so the library now exports _ZN10Calculator3addEii. Consumers compiled against v1 keep using their own inlined copy (the new export is simply unused by them); consumers compiled against v2 call the newly-exported symbol. Nothing that worked before stops working โ€” this is a pure addition.

Old/new diff

old/lib.hpp new/lib.hpp
inline int add(int a, int b) { return a + b; } (no exported symbol) int add(int a, int b); โ€” body in lib.cpp, symbol exported

abicheck command

g++ -shared -fPIC -g -std=c++17 old/lib.cpp -Iold -o libcalc_v1.so
g++ -shared -fPIC -g -std=c++17 new/lib.cpp -Inew -o libcalc_v2.so
abicheck compare libcalc_v1.so libcalc_v2.so

Expected abicheck finding

Verdict: COMPATIBLE (exit 0)

Additions:
- func_added: New public function: Calculator::add(int, int)
  > New function available; existing binaries are unaffected.

Minimum evidence

min_evidence: L0 โ€” the exported-symbol table alone is enough: add's mangled symbol is absent from v1's .dynsym and present in v2's. No debug info or headers needed to see that a new public symbol appeared.

Why abicheck catches it

abicheck diffs the two libraries' exported-symbol sets directly from ELF; a demangled symbol present only in the new snapshot is reported as func_added, a compatible addition, since no existing symbol changed or disappeared.

Runtime failure demonstration

No observable effect on existing binaries โ€” add()/multiply()/subtract() return identical results both before and after the swap, whether the app is compiled against v1's inline body or v2's exported symbol.

# Build old library + app (compiled against v1's inline add())
g++ -shared -fPIC -g -std=c++17 old/lib.cpp -Iold -o libcalc.so
g++ -g -std=c++17 app.cpp -Iold -L. -lcalc -Wl,-rpath,. -o app
./app
# โ†’ add=5 multiply=20 subtract=8
# โ†’ expected: 5 20 8

# Swap in new library (no recompile)
g++ -shared -fPIC -g -std=c++17 new/lib.cpp -Inew -o libcalc.so
./app
# โ†’ add=5 multiply=20 subtract=8   (identical โ€” app used its own inlined add())

# Confirm the symbol table difference directly:
nm -D libcalc_v1.so | grep add   # โ†’ (nothing โ€” add() was inlined, no symbol)
nm -D libcalc_v2.so | grep add   # โ†’ T _ZN10Calculator3addEii

Safe redesign

This transition is already safe for the binary-vs-binary case above, but it does introduce a build-coordination hazard that a pure library diff can't see: a consumer compiled against v2's header (declaration only, no inline body) but linked against v1's .so (which never exported add โ€” it was inline) gets a hard link error, undefined reference to Calculator::add(int, int). That's not an ABI break in the usual sense โ€” no existing binary stops working โ€” but it does mean v2's header and v2's .so must ship together:

Consumer built against Runtime .so Result
v1 header (inline, no symbol) v1 .so works โ€” caller uses its own inlined copy
v1 header (inline, no symbol) v2 .so (symbol exported) works โ€” caller's inlined copy is used; the new export is unused
v2 header (declaration only) v2 .so (symbol exported) works โ€” caller resolves the exported symbol
v2 header (declaration only) v1 .so (no symbol) link failure โ€” add was never exported by v1

Only the last row fails, and it requires a specific build mismatch (new headers paired with an old library) that a coordinated release naturally avoids. See case16 (inline_to_non_inline) for the same mechanism spelled out end to end with a free function.

Cross-tool comparison

abidw --out-file v1.xml libcalc_v1.so
abidw --out-file v2.xml libcalc_v2.so
abidiff v1.xml v2.xml

References


Source files

  • CMakeLists.txt
  • app.cpp

See also: Examples overview ยท All COMPATIBLE cases ยท Category: Addition (Compatible).