Skip to content

Case 22: Method Const Qualifier Changed

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

Category: Breaking | Verdict: ๐Ÿ”ด BREAKING

Verdict and consumer impact

In the Itanium C++ ABI, const qualification on a member function is encoded directly into the mangled symbol name: Widget::get() const mangles to _ZNK6Widget3getEv (the K marks const); dropping const mangles to _ZN6Widget3getEv โ€” a completely different symbol. Old binaries import _ZNK6Widget3getEv; new's library exports only _ZN6Widget3getEv. The dynamic linker cannot resolve the reference โ€” an unresolved-symbol error at load or first-call time, not a subtle miscalculation.

Old/new diff

old/lib.h new/lib.h
class Widget { public: void get() const; }; class Widget { public: void get(); };

abicheck command

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

Expected abicheck finding

Verdict: BREAKING (exit 4)

- func_removed_elf_only: Elf_only function removed: Widget::get() const
  > Exported function symbol removed from the binary; old binaries that
    link or dlsym() it can fail even without header evidence.

Additions:
- func_added: New public function: Widget::get()
  > New function available; existing binaries are unaffected.

Minimum evidence

min_evidence: L1 โ€” because const is baked into the Itanium mangled name itself, the exported-symbol table already shows two unrelated symbols (_ZNK6Widget3getEv gone, _ZN6Widget3getEv new) โ€” this is detectable from ELF/DWARF symbol evidence alone, no public headers required. Headers (L2) sharpen the finding from a generic removed+added pair into a named CV-qualifier change โ€” see below โ€” but aren't needed to reach the BREAKING verdict.

Why abicheck catches it

At L1, abicheck's exported-symbol diff sees _ZNK6Widget3getEv present only in old and _ZN6Widget3getEv present only in new, with no other correlating evidence to link them, so it reports a straightforward func_removed_elf_only + func_added pair โ€” already BREAKING, since a symbol old binaries depend on is gone. With header AST evidence (L2), the same two functions are recognized as the same declaration source (Widget::get, old/lib.h:3) with a changed const qualifier, so the finding gets renamed to the more specific func_cv_changed:

abicheck compare libfoo_v1.so libfoo_v2.so \
  --header old=old/lib.h --header new=new/lib.h --ast-frontend clang
- func_cv_changed: CV qualifier changed: get (const=True volatile=False ->
  const=False volatile=False)
  > const/volatile on 'this' changes the mangled name; old binaries link to
    the wrong symbol.

Runtime failure demonstration

Severity: CRITICAL

Scenario: app compiled against old header calls get() const (_ZNK6Widget3getEv). new's library exports only _ZN6Widget3getEv โ€” undefined symbol at runtime.

# Build old library + app
g++ -shared -fPIC -std=c++17 -g old/lib.cpp -Iold -o libwidget.so
g++ -std=c++17 -g app.cpp -Iold -L. -lwidget -Wl,-rpath,. -o app
./app
# โ†’ get() const called
# โ†’ exit: 0

# Swap in new library (const removed)
g++ -shared -fPIC -std=c++17 -g new/lib.cpp -Inew -o libwidget.so
./app
# โ†’ ./app: symbol lookup error: ./app: undefined symbol: _ZNK6Widget3getEv
# โ†’ exit: 127

Why CRITICAL: const is part of the C++ mangled name (K in _ZNK...). Removing it produces a completely different symbol. Every pre-built binary that calls widget.get() on a const reference fails to load โ€” a hard runtime crash, not a silent miscalculation.

Safe redesign

Keep the old const-qualified overload and add the new non-const one alongside it if the semantics genuinely need to differ; if the const qualifier was simply wrong, ship both overloads during a transition period so binaries built against either one keep resolving.

Real-world example: const-correctness cleanups that touch an already-shipped public method signature are a classic source of this โ€” every mangled-name change from a const fix is, by ABI rules, indistinguishable from removing one function and adding an unrelated one.

References


Source files

  • CMakeLists.txt
  • app.cpp

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