Skip to content

Case 100: Experimental Declaration Removed Without Replacement

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

Category: Symbol API | Verdict: โŒ BREAKING

Verdict and consumer impact

lib::experimental::bar() only ever lived under the experimental:: namespace in v1. v2 deletes it outright with no stable-namespace replacement โ€” no lib::bar() was ever published to migrate to. Any consumer that named the experimental declaration fails to link against v2: the mangled symbol is simply gone, and there is no successor API to recompile against.

Old/new diff

v1.h v2.h
namespace lib { namespace experimental { void bar(); } } (removed โ€” no lib::bar or any stable twin published)

v2.h keeps an unrelated lib::unrelated() only so the library still exports something; it is not a replacement for bar().

abicheck command

clang++ -std=c++17 -shared -fPIC -g v1.cpp -o liblib_v1.so
clang++ -std=c++17 -shared -fPIC -g v2.cpp -o liblib_v2.so
abicheck compare liblib_v1.so liblib_v2.so

Expected abicheck finding

Verdict: BREAKING (exit 4)

## Breaking Changes

- func_removed: Public function removed: lib::experimental::bar
  > Old binaries call a symbol that no longer exists; dynamic linker
    will refuse to load or crash at call site.

## Source-Level Breaks

- experimental_removed_without_replacement: Experimental declaration
  'lib::experimental::bar' was removed and no declaration with leaf 'bar'
  was published at a stable namespace in the new headers.
  > Consumers that depended on the experimental name no longer compile.

Minimum evidence

min_evidence: L0 โ€” the mangled symbol _ZN3lib12experimental3barEv disappearing from v2's .dynsym is enough on its own to reach the BREAKING verdict, even on a fully stripped binary with no debug info at all (as func_removed_elf_only). Classifying it specifically as "no replacement published" needs the declared qualified name, which the namespace-shape detector recovers from DWARF (L1) or header text (L2), not from the raw mangled symbol; no public headers are required as long as DWARF is present. Note: this fixture's DWARF, produced by clang, records each function's DW_AT_name directly on the exported definition DIE; a GCC build of the same source links the definition to its declaration purely via DW_AT_specification without repeating the name, which the current DWARF walker doesn't yet resolve back to a qualified name โ€” so with GCC this reduces to the plain func_removed_elf_only finding without the named diagnosis, same as the fully-stripped case. Either toolchain reaches the same BREAKING verdict.

Why abicheck catches it

func_removed comes from the standard DWARF/symbol-table diff โ€” bar()'s mangled name disappears between snapshots. The dedicated experimental_removed_without_replacement finding is a generic namespace-shape detector (diff_namespaces.py): for every public declaration whose qualified name contains an experimental-labeled segment in the old snapshot, it looks up the same leaf name in the new snapshot. If the experimental spelling is gone and no stable-namespace twin exists, it reports the removal as "no migration target" rather than a generic symbol removal โ€” the framing a reviewer actually needs.

Runtime failure demonstration

Severity: CRITICAL

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

# Build old library + app
ln -sf liblib_v1.so liblib.so
clang++ -std=c++17 app.cpp -L. -llib -Wl,-rpath,. -o app
./app
# โ†’ exits 0 (lib::experimental::bar() resolves and runs)

# Swap in new library (no recompile)
ln -sf liblib_v2.so liblib.so
./app
# โ†’ ./app: symbol lookup error: ./app: undefined symbol: _ZN3lib12experimental3barEv

Why CRITICAL: bar() is absent from v2's exported symbols; the dynamic linker cannot resolve it and the process fails to start.

Safe redesign

Don't delete an experimental:: declaration outright once it has real consumers โ€” either graduate it to a stable namespace (keeping the experimental name as a deprecated alias for one release) or, if it truly must be dropped, document the removal explicitly and give consumers a migration target before the next release, not after.

Real-world example: a research-stage algorithm published under oneapi::dpl::experimental::ranges and pulled without ever being promoted to the stable surface โ€” the "experimental" label is a real contract that it may disappear, but "disappear silently with nothing to migrate to" is still the failure mode reviewers want flagged explicitly.

Cross-tool comparison

abidiff (binary-only) would see the same mangled-symbol removal and report it as a function removal; it has no notion of "experimental namespace" or "no stable replacement" โ€” that framing is unique to abicheck's namespace-shape detector. Not re-verified numerically in this environment (abidiff is not installed here).

References


Source files

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

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