Case 108: task Class Removed (historical ABI break โ vtable angle)¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS, Windows |
| Flags | ABI break, API break |
Detected ChangeKinds |
func_removed, type_removed |
| Source files | examples/case108_task_class_removed/ |
Category: Polymorphic Class Removal | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
An entire publicly-derivable polymorphic base class is removed. Every user
subclass that overrode the virtual execute() becomes a vtable error at
link/load time: the typeinfo symbol for the v1 base is gone, RTTI fails, and
delete on a polymorphic pointer to the removed base invokes UB. This is
more severe than a plain class removal (case107) because the base class was
a derivation point โ user code embedded the v1 vtable layout into every
derived class's own vtable, and the RTTI strings crossed DSO boundaries.
Old/new diff¶
| v1.h | v2.h |
|---|---|
class task { public: task(); virtual ~task(); virtual task* execute() = 0; ... }; |
(removed) |
task* mylib_spawn_dummy(); |
(removed) |
| โ | class task_group { public: void run(task_fn); void wait(); }; |
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)
- type_removed: Type removed: mylib::task
> Old code references a type that no longer exists; compilation or link
failure.
- type_removed: Type removed: mylib::dummy_task
- func_removed_elf_only: Elf_only function removed: mylib::mylib_spawn_dummy()
> (and 6 more: task ctor/dtor/set_ref_count/decrement_ref_count)
- var_removed: Public variable removed: vtable for mylib::task
- var_removed: Public variable removed: typeinfo for mylib::task
- var_removed: Public variable removed: typeinfo name for mylib::task
Additions:
- type_added: New type: mylib::task_group
- func_added: New public function: mylib::task_group::run/wait/task_group()
10 genuine public-surface breaking findings (3 of 13 raw findings are
internal RTTI/dependency churn, annotated separately by the reporter).
Minimum evidence¶
min_evidence: L1 โ task's ctor/dtor/methods, its vtable, and its
typeinfo symbols are all emitted from v1.cpp (which instantiates a
dummy_task subclass), so DWARF + the dynamic symbol table are enough; no
public headers are required.
Why abicheck catches it¶
DWARF exposes the class hierarchy and virtual member layout directly, and
the ELF symbol table carries the mangled vtable (_ZTV7mylib4task) and
typeinfo (_ZTI7mylib4task) symbols alongside the ordinary member
functions. When the whole class disappears in v2, abicheck's DWARF/ELF diff
reports the type, its members, and its vtable/typeinfo variables as removed
together โ the full surface of what "removing a polymorphic base" actually
costs a consumer.
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++ -g app.cpp -L. -lfoo -Wl,-rpath,. -o app
./app
# โ ref_count after dec = 2 (expect 2)
# Swap in new library (no recompile)
g++ -shared -fPIC -g v2.cpp -o libfoo.so
./app
# โ ./app: symbol lookup error: ./app: undefined symbol:
# _ZN5mylib17mylib_spawn_dummyEv
# Source rebuild against the v2 header also fails: the base class and
# factory are both gone.
# โ error: 'task' is not a member of 'mylib'
# โ error: 'mylib_spawn_dummy' is not a member of 'mylib'
Why BREAKING: app calls the factory mylib_spawn_dummy() to obtain a
polymorphic task*; that symbol โ along with the vtable and typeinfo it
depends on โ no longer exists in v2, so the dynamic linker refuses to
resolve it before main() even runs.
Safe redesign¶
A polymorphic base class is the most expensive thing to remove. The safe
migration is:
1. Keep the old base class header in a legacy/ subdir for one release.
2. Provide an adapter that wraps the new API in the old interface.
3. Bump SONAME on removal.
4. Document the equivalence map (which task workflow maps to which
task_group call).
Real-world example: classic TBB's tbb::task low-level API was the
recommended way to write parallel algorithms before parallel_invoke /
task_group. It was fully removed in oneTBB 2021.1 alongside
task_scheduler_init (case107).
References¶
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.