Skip to content

Case 24 โ€” Union Field Removed

Field Value
Verdict ๐Ÿ”ด BREAKING
Category Breaking
Platforms Linux, macOS, Windows
Flags ABI break, API break
Detected ChangeKinds union_field_removed
Source files browse on GitHub

Verdict: ๐Ÿ”ด BREAKING abicheck verdict: BREAKING

What changes

Version Definition
v1 union Data { int i; float f; };
v2 union Data { int i; };

What breaks at binary level

Removing a union field removes a supported representation of the shared storage. While the remaining field (int i) is still at offset 0 and accessible, any code that was compiled to use the float f variant is now accessing an undefined member.

If the removal also reduces the union's size (e.g., removing a double field from a union of int and double), existing code that allocates sizeof(union Data) with the old size will over-allocate (harmless) or under-allocate if the field was the largest member (harmful โ€” but this is caught by TYPE_SIZE_CHANGED).

The semantic break is the main concern: the removed field was part of the type's public contract, and consumers relied on it as a valid interpretation.

Note: Adding a union field is classified as COMPATIBLE because all fields share offset 0 and existing fields are unaffected. Size increases are caught separately.

Consumer impact

/* consumer compiled against v1 */
union Data d;
d.f = 3.14f;   /* valid in v1 */
lib_consume(d); /* library no longer expects float variant */

/* v2: 'f' field doesn't exist โ€” semantic mismatch */

Mitigation

  • Keep union variants stable across ABI-compatible releases.
  • Introduce versioned replacement types (e.g., DataV2) when the union contract must change.
  • Use tagged unions with explicit discriminators to manage variant evolution.

Code diff

-union Data { int i; float f; };
+union Data { int i; };

Real Failure Demo

Severity: CRITICAL

Scenario: app reads d.f as float after init_data(). With v2 the function writes d.i = 42 โ€” reading as float gives garbage.

# Build old lib + app
gcc -shared -fPIC -g old/lib.c -Iold -o libdata.so
gcc -g app.c -Iold -L. -ldata -Wl,-rpath,. -o app
./app
# โ†’ d.f = 3.140000

# Swap in new lib (writes int 42 instead of float 3.14)
gcc -shared -fPIC -g new/lib.c -Inew -o libdata.so
./app
# โ†’ d.f = 0.000000 (expected 3.14, got wrong value with v2)
# (integer 42 reinterpreted as IEEE 754 float = ~5.88e-44)

Why CRITICAL: The library writes integer bits, the caller reads float bits from the same storage. Silent wrong value โ€” no crash, no error, just completely wrong data.

Why runtime result may differ from verdict

Union field removed: accessing removed field reads undefined memory

Runtime note

Runtime check now fails explicitly when removed union member changes data interpretation.

References


Source files

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