Skip to content

Case 37 -- Base Class Changes

Field Value
Verdict 🔴 BREAKING
Category Breaking
Platforms Linux, macOS, Windows
Flags ABI break, API break
Detected ChangeKinds type_base_changed
Source files browse on GitHub

Verdict: 🔴 BREAKING abicheck verdict: BREAKING

What changes

Version Definition
v1 ReorderDemo : Logger, Serializer / VirtualDemo : Logger / AddBaseDemo : Logger
v2 ReorderDemo : Serializer, Logger (swapped) / VirtualDemo : virtual Logger / AddBaseDemo : Logger, Serializer (added base)

Three separate scenarios are demonstrated:

  1. Base class position changed -- ReorderDemo swaps base order
  2. Base class became virtual -- VirtualDemo gains virtual inheritance
  3. Base class added -- AddBaseDemo acquires Serializer as a second base

Why this is a binary ABI break

All three changes alter the object layout:

  1. Reorder: In multiple inheritance, each base class occupies a sub-object at a specific offset. Swapping the order changes which vtable pointer is at offset 0 and how this is adjusted when casting to a base pointer. Code compiled against v1 that casts ReorderDemo* to Logger* will get the wrong sub-object with v2.

  2. Virtual inheritance: Changing from non-virtual to virtual inheritance completely restructures the object layout, introducing a vbase offset and moving the base sub-object to the end of the most-derived object.

  3. Added base: Adding Serializer as a base increases the object size and shifts field offsets. Code compiled with v1's layout will read/write wrong memory locations.

Code diff

-class ReorderDemo : public Logger, public Serializer {
+class ReorderDemo : public Serializer, public Logger {
     // base order swapped -> this-pointer adjustments change

-class VirtualDemo : public Logger {
+class VirtualDemo : public virtual Logger {
     // non-virtual -> virtual inheritance -> layout restructured

-class AddBaseDemo : public Logger {
+class AddBaseDemo : public Logger, public Serializer {
     // new base added -> object size grows, offsets shift

Real Failure Demo

Severity: CRITICAL

# Build v1 lib + app
g++ -shared -fPIC -g v1.cpp -o libv1.so
g++ -g app.cpp -I. -L. -lv1 -Wl,-rpath,. -o app
./app
# -> ReorderDemo: log_level=1, format=2
# -> ReorderDemo::log() called via Logger* OK
# -> ReorderDemo::serialize() called via Serializer* OK
# -> VirtualDemo: log_level=5
# -> AddBaseDemo: log_level=3

# Swap to v2
g++ -shared -fPIC -g v2.cpp -o libv1.so
./app
# -> CRASH or wrong output: Logger* now points to Serializer sub-object
#    due to swapped base order. Virtual method calls through base pointers
#    invoke the wrong function or pass a corrupted this pointer.

Why CRITICAL: Base class layout changes silently corrupt the object's memory layout. The this-pointer adjustments compiled into the application no longer match the library's actual object layout, causing virtual dispatch to call the wrong function, or field accesses to read/write the wrong memory -- both leading to crashes or silent data corruption.

Why runtime result may differ from verdict

Base class position changed: derived class layout corrupted

Runtime note

Methods now mutate fields and app asserts expected postconditions; layout/base-order mismatch is observable.

abicheck Detection

abicheck detects base class changes when run with header files (-H):

make  # builds libv1.so and libv2.so with -g debug info

python3 -m abicheck.cli dump libv1.so -H v1.hpp -o v1.json
python3 -m abicheck.cli dump libv2.so -H v2.hpp -o v2.json
python3 -m abicheck.cli compare v1.json v2.json
# → BREAKING: type_base_changed, type_size_changed, type_vtable_changed

Note: Without -H header files, abicheck reports NO_CHANGE because the ELF symbol table does not encode C++ class hierarchy information. Header analysis is required for C++ structural changes (base class composition, vtable layout, field offsets).

References


Source files

See also: Examples overview · All BREAKING cases · Category: Breaking.