Case 41: Type-Level Changes¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS |
| Flags | ABI break, API break |
Detected ChangeKinds |
type_removed, func_removed, enum_member_added, enum_last_member_value_changed |
| Source files | examples/case41_type_changes/ |
Category: Type / Enum Changes | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
Four independent type-level changes land in one release: struct
LegacyConfig (and the function that used it, process_config()) is
removed and replaced with an unrelated struct NewConfig; AlignedBuffer's
required alignment grows from 8 to 64 bytes; and the priority_t enum
gains a new member that shifts its PRIO_MAX sentinel from 3 to 4. Any
binary that calls process_config(), allocates an AlignedBuffer at the
old alignment, or relies on PRIO_MAX as a bound is broken without
recompilation.
Old/new diff¶
| v1.h | v2.h |
|---|---|
struct LegacyConfig { int mode; int flags; }; |
(removed) |
| โ | struct NewConfig { int mode; int flags; int version; }; (added) |
struct __attribute__((aligned(8))) AlignedBuffer |
struct __attribute__((aligned(64))) AlignedBuffer |
PRIO_HIGH = 2, PRIO_MAX = 3 |
PRIO_HIGH = 2, PRIO_URGENT = 3, PRIO_MAX = 4 |
void process_config(struct LegacyConfig *cfg); |
(removed) |
abicheck command¶
gcc -shared -fPIC -g v1.c -o libfoo_v1.so
gcc -shared -fPIC -g v2.c -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- type_removed: Type removed: LegacyConfig
> Old code references a type that no longer exists; compilation or
link failure.
Affected symbols: process_config
- type_alignment_changed: Alignment changed: AlignedBuffer (64 -> 512 bits)
> Misaligned access can cause bus errors on strict architectures or
silent data corruption with SIMD.
Affected symbols: fill_buffer
- func_removed: Public function removed: process_config
> Old binaries call a symbol that no longer exists; dynamic linker
will refuse to load or crash at call site.
Deployment Risk:
- enum_last_member_value_changed: Enum member value changed:
priority_t::PRIO_MAX (3 -> 4)
Additions:
- enum_member_added: Enum member added: priority_t::PRIO_URGENT (3)
> New enumerator may shift subsequent values in non-fixed enums;
switch defaults may miss the new case.
Minimum evidence¶
min_evidence: L1 โ DWARF carries each type's existence
(DW_TAG_structure_type), alignment (DW_AT_alignment), and each enum's
member list with values (DW_TAG_enumerator); combined with the
exported-symbol table for process_config's removal, that's enough to
detect all four changes. No public headers required.
Why abicheck catches it¶
DWARF records every type reachable from the library's debug info, so a
struct present in v1's type table and absent from v2's is caught as
type_removed; DW_AT_alignment on the struct DIE carries the alignment
change directly; and each DW_TAG_enumeration_type's enumerator list
(name + DW_AT_const_value) lets abicheck diff the enum member set and
detect that the last member's value shifted.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: compile app against v1, swap in v2 .so without recompile.
# Build old library + app
gcc -shared -fPIC -g v1.c -o libfoo.so
gcc -g app.c -I. -L. -lfoo -Wl,-rpath,. -o app
./app
# โ process_config(mode=1, flags=255)
# โ fill_buffer (alignof=8, sizeof=64)
# โ set_priority(PRIO_HIGH=2)
# โ PRIO_MAX = 3 (sentinel)
# โ exit 0
# Swap in new library (no recompile)
gcc -shared -fPIC -g v2.c -o libfoo.so
./app
# โ ./app: symbol lookup error: ./app: undefined symbol: process_config
# โ exit 127
Why CRITICAL: process_config is removed from v2's exported symbols,
so the dynamic linker refuses to resolve it and the process cannot even
start. Had that symbol stayed around, the app would have gone on to
allocate AlignedBuffer at the wrong (8-byte) alignment against a library
that expects 64-byte alignment, and used the stale PRIO_MAX=3 sentinel
against a library where PRIO_URGENT=3 is now a valid priority โ both
silent-corruption classes of break layered underneath the hard link
failure.
Safe redesign¶
Never remove a public type or function without a SONAME bump; keep the old
symbol deprecated for at least one cycle. Wrap alignment-sensitive types
behind an opaque pointer so alignment changes are invisible to callers.
Avoid using an enum's last member as an implicit array bound or loop limit
in a public API โ expose a separate #define/constant or a query function
instead, so inserting a new enumerator doesn't silently move the sentinel.
Real-world example: SIMD-oriented libraries commonly raise a buffer type's required alignment (e.g. for AVX-512) in a "performance" release โ without a SONAME bump this breaks every caller that stack-allocated the old, more loosely aligned struct.
Cross-tool comparison¶
abidw --out-file v1.xml libv1.so
abidw --out-file v2.xml libv2.so
abidiff v1.xml v2.xml
echo "exit: $?" # โ 12 (ABI change + breaking)
References¶
Source files¶
CMakeLists.txtapp.cv1.cv1.hv2.cv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.