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¶
- 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. - Qualify at birth: decide
&/&&when the method is first shipped; qualifiers on day one cost nothing. - 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¶
- Itanium C++ ABI โ mangling,
<nested-name>ref-qualifier - KDE ABI Policy โ Binary Compatibility Issues
- P0798 /
std::optional::value() &&โ the move-out accessor idiom
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.