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
What breaks¶
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 case107 because:
- The base class was a derivation point โ user code embedded the v1 vtable
layout into every derived class's vtable.
- RTTI strings (typeinfo for mylib::task) crossed DSO boundaries; removing
the base silently breaks dynamic_cast and exception handling for any
user exception derived from it.
Real Failure Demo¶
Severity: BREAKING
cmake -S examples -B /tmp/abicheck-examples-build -DCMAKE_BUILD_TYPE=Debug
cmake --build /tmp/abicheck-examples-build \
--target case95_task_class_removed_app case95_task_class_removed_v2
/tmp/abicheck-examples-build/case95_task_class_removed/app_v1
# ref_count after dec = 2 (expect 2)
# Runtime replacement: the v1 binary asks for the removed v1 factory/type
# surface and fails as soon as the missing symbol is resolved.
tmp=$(mktemp -d)
cp /tmp/abicheck-examples-build/case95_task_class_removed/app_v1 "$tmp/"
cp /tmp/abicheck-examples-build/case95_task_class_removed/libv2.so "$tmp/libv1.so"
(cd "$tmp" && LD_LIBRARY_PATH=. ./app_v1)
# ./app_v1: symbol lookup error: ./app_v1: undefined symbol: _ZN5mylib17mylib_spawn_dummyEv
# Source rebuild against the v2 header also fails because the base class and
# factory are gone.
tmp=$(mktemp -d)
cp examples/case95_task_class_removed/app.cpp "$tmp/app.cpp"
cp examples/case95_task_class_removed/v2.h "$tmp/v1.h"
g++ -std=c++17 -I"$tmp" -c "$tmp/app.cpp" -o "$tmp/app.o"
# error: 'task' is not a member of 'mylib'
# error: 'mylib_spawn_dummy' is not a member of 'mylib'
Why this matters¶
This fixture mirrors a real historical ABI break. Classic TBB's tbb::task low-level API was the recommended way to write
parallel algorithms before parallel_invoke / task_group. The class was
fully removed in oneTBB 2021.1 along with task_scheduler_init (case107).
Code diff¶
| v1 | v2 |
|---|---|
class task { virtual task* execute() = 0; ... }; |
(removed) |
task* mylib_spawn_dummy(); |
(removed) |
| โ | class task_group { using task_fn = void (*)(); void run(task_fn); ... }; |
How abicheck catches it¶
The existing FUNC_REMOVED / TYPE_REMOVED detectors fire on the base
class symbols, vtable symbol (_ZTV7mylib4task), and typeinfo
(_ZTI7mylib4task). This case exists to pin the full surface as a named
regression fixture.
How to fix¶
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).
References¶
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.