Case 110: concurrent_unordered_map API Drift¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS |
| Flags | ABI break, API break |
Detected ChangeKinds |
func_removed |
| Source files | examples/case110_concurrent_unordered_map_api_drift/ |
Category: ABI + source break / regression suite | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
A public member function (insert) loses one of its parameters between
versions. The mangled symbol of the old 2-argument overload disappears from
the .so; the new 1-argument overload has a different mangled name. Any
consumer binary that called insert(key, rehash_hint) gets undefined
symbol at load time against v2, and any consumer source that called it
fails to compile against v2's headers โ both the link and the source break
at once.
Old/new diff¶
| v1.h | v2.h |
|---|---|
void insert(int key, unsigned long rehash_hint); |
void insert(int key); |
abicheck command¶
g++ -std=c++17 -shared -fPIC -g v1.cpp -o libfoo_v1.so
g++ -std=c++17 -shared -fPIC -g v2.cpp -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:
mylib::concurrent_unordered_map_int::insert(int, unsigned long)
> 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:
mylib::concurrent_unordered_map_int::insert(int)
Minimum evidence¶
min_evidence: L0 โ the two overloads have different mangled names
(...insertEim vs ...insertEi), so the exported-symbol table alone shows
the old symbol gone and a new one in its place. No debug info or headers
needed; -g above is only there so the Runtime failure demonstration
below can build a matching app.
Why abicheck catches it¶
C++ name mangling encodes the parameter list, so a signature change produces a structurally different symbol. abicheck's L0 symbol-table diff sees the old mangled name vanish and a new one appear, and reports it as a public function removal (BREAKING) plus a public function addition (informational โ helps the reporter suggest a candidate replacement).
Runtime failure demonstration¶
Severity: BREAKING
Scenario: compile app against v1, swap in v2 .so without recompile.
# Build old library + app
g++ -shared -fPIC -g v1.cpp -o libfoo.so
g++ -std=c++17 -g app.cpp -L. -lfoo -Wl,-rpath,. -o app
./app; echo $?
# โ exit 1 (m.insert(42, 8) then m.size() == 1)
# Swap in new library (no recompile)
g++ -shared -fPIC -g v2.cpp -o libfoo.so
./app
# โ ./app: symbol lookup error: ./app: undefined symbol:
# _ZN5mylib28concurrent_unordered_map_int6insertEim
# Source rebuild against the v2 header also fails:
# โ error: no matching function for call to 'insert(int, int)'
Why BREAKING: the app calls the 2-argument insert, whose mangled
symbol is unique to the old parameter list; v2's .so exports only the
1-argument overload under a different mangled name, so the dynamic linker
cannot resolve the call and the process is killed on startup.
Safe redesign¶
- Keep both overloads through a deprecation window: in release Nโ1, mark
the hint-form overload
[[deprecated]]and have it call into the hint-free implementation. In release N, remove it. - If the hint argument is genuinely dead, prefer making it a default
argument first (
unsigned long rehash_hint = 0) โ that preserves the symbol name on platforms where the mangled signature includes the parameter, and only later remove it.
Real-world example: mirrors several documented historical signature
tightenings on tbb::concurrent_unordered_map (and friends) where helper /
hint parameters were dropped or reordered between releases.
References¶
- oneTBB 2021 migration guide:
concurrent_unordered_mapAPI changes.
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.