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:
- Base class position changed --
ReorderDemoswaps base order - Base class became virtual --
VirtualDemogainsvirtualinheritance - Base class added --
AddBaseDemoacquiresSerializeras a second base
Why this is a binary ABI break¶
All three changes alter the object layout:
-
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
thisis adjusted when casting to a base pointer. Code compiled against v1 that castsReorderDemo*toLogger*will get the wrong sub-object with v2. -
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.
-
Added base: Adding
Serializeras 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.