Skip to content

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.txt
  • app.cpp
  • v1.cpp
  • v1.h
  • v2.cpp
  • v2.h

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