Skip to content

Case 166: Method Ref-Qualifier Added (str() โ†’ str() &)

Field Value
Verdict ๐Ÿ”ด BREAKING
Category Breaking
Platforms Linux, macOS
Flags ABI break, API break
Detected ChangeKinds func_ref_qual_changed
Source files examples/case166_ref_qualifier_added/

Category: Function Signature / Mangling | Verdict: ๐Ÿ”ด BREAKING

Verdict and consumer impact

v2 adds an lvalue ref-qualifier to MessageBuilder::str(). The motivation is sound API hardening โ€” MessageBuilder().str() returns a pointer into a temporary that dies at the end of the expression, and & makes that dangling call a compile error โ€” but the ref-qualifier is part of the Itanium mangling, encoded right after the CV-qualifiers in the nested-name. The old symbol _ZN14MessageBuilder3strEv vanishes from the library entirely (the new one is _ZNR14MessageBuilder3strEv), so every existing binary fails at load time with an undefined-symbol error โ€” recompilation is mandatory even though no call site's source changed.

Old/new diff

v1.h v2.h
const char* str(); (_ZN14MessageBuilder3strEv) const char* str() &; (_ZNR14MessageBuilder3strEv)

abicheck command

g++ -shared -fPIC -g v1.cpp -o libv1.so
g++ -shared -fPIC -g v2.cpp -o libv2.so
abicheck compare libv1.so libv2.so --header old=v1.h --header new=v2.h --ast-frontend clang

Expected abicheck finding

Verdict: BREAKING (exit 4)

- func_removed: Public function removed: str
  > Old binaries call a symbol that no longer exists; dynamic linker
    will refuse to load or crash at call site.
- func_ref_qual_changed: Ref-qualifier changed: str ('' โ†’ '&')
  > Ref-qualifier (&/&&) on a member function changed; this alters the
    Itanium C++ ABI mangled name and overload resolution, so old
    binaries link to the wrong symbol or fail to resolve it.

Additions:
- func_added: New public function: str

abicheck matches the removed and added declarations by (name, parameters) and reports the pair as func_ref_qual_changed instead of leaving an unexplained removed+added pair unlinked.

Minimum evidence

min_evidence: L2 โ€” released castxml versions do not emit a ref-qualifier attribute on the AST node, so abicheck needs the public header AST plus the Itanium mangling (_ZNRโ€ฆ/_ZNOโ€ฆ) to recover the qualifier; DWARF alone does not carry it. castxml is the documented default backend for this evidence layer; clang (--ast-frontend clang, used above) is a supported alternative AST frontend for hosts without castxml installed.

Why abicheck catches it

abicheck parses the public header AST for each side's str() declaration and cross-references the mangled symbol name in .dynsym; the ref-qualifier bit recovered from the mangling (R = &, O = &&, absent = unqualified) lets it link the "removed" old symbol and "added" new symbol as one func_ref_qual_changed event rather than an opaque removal/addition pair.

Runtime failure demonstration

Severity: CRITICAL

Scenario: compile app against v1, swap in v2 .so without recompile.

# Build old library + app
g++ -shared -fPIC -g v1.cpp -o liblib.so
g++ -g app.cpp -L. -llib -Wl,-rpath,. -o app
./app
# โ†’ message = status=ok (expected status=ok)

# Swap in new library (no recompile)
g++ -shared -fPIC -g v2.cpp -o liblib.so
./app
# โ†’ ./app: symbol lookup error: ./app: undefined symbol: _ZN14MessageBuilder3strEv

Why CRITICAL: the app aborts before main() finishes symbol binding โ€” the classic undefined-symbol loader failure โ€” even though no call site in the app's source changed.

Safe redesign

  1. Add, don't replace: keep the unqualified str() exported (possibly as a deprecated out-of-line definition forwarding to the qualified one) until the next SONAME bump.
  2. Qualify at birth: decide &/&& when the method is first shipped; qualifiers on day one cost nothing.
  3. SONAME bump if the hardening must land now.

Real-world example: this is the same mechanics as a const-qualifier break, which the KDE binary-compatibility policy lists among the "you cannot..." rules: any change to a function's cv- or ref-qualification changes the mangled name. The &&-qualified accessor idiom popularized by std::optional::value() && (C++17) made retrofit-qualifying older accessors a recurring temptation in library changelogs.

References


Source files

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

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