Case 08: Enum Value Change¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux |
| Flags | ABI break, API break |
Detected ChangeKinds |
enum_member_value_changed |
| Source files | examples/case08_enum_value_change/ |
Category: Type Layout | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
Any code compiled against v1 that compares against or stores the numeric
value 1 for GREEN now silently means YELLOW under v2. Serialized data
(files, network packets, saved config) using the old integer values is
misinterpreted after the swap โ there is no crash, just wrong behavior.
Old/new diff¶
| v1.h | v2.h |
|---|---|
{ RED=0, GREEN=1, BLUE=2 } |
{ RED=0, YELLOW=1, GREEN=2, BLUE=3 } |
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)
- enum_member_value_changed: Enum member value changed: Color::GREEN (1 -> 2)
> Old binaries use stale numeric values; logic comparisons and switch
statements silently break.
- enum_member_value_changed: Enum member value changed: Color::BLUE (2 -> 3)
> Old binaries use stale numeric values; logic comparisons and switch
statements silently break.
Additions:
- enum_member_added: Enum member added: Color::YELLOW (1)
> New enumerator may shift subsequent values in non-fixed enums; switch
defaults may miss the new case.
Minimum evidence¶
min_evidence: L1 โ DWARF's enumerator entries (DW_TAG_enumerator) record
each member's name and integer value for both versions, so -g alone is
enough to detect the renumbering; no public headers required.
Why abicheck catches it¶
DWARF preserves each enum member's value alongside its name; abicheck diffs the two versions' enumerator lists by name and flags any member whose associated integer value changed, independent of any textual reordering in source.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: app compiled with v1 (GREEN=1) calls get_signal(), which
returns GREEN. With v2, GREEN shifted to 2 โ the app's compiled-in
comparison against 1 no longer matches.
# Build v1 + app
gcc -shared -fPIC -g v1.c -o libfoo.so
gcc -g app.c -I. -L. -lfoo -Wl,-rpath,. -o app
./app
# โ GREEN (correct)
# Swap in v2 (no recompile)
gcc -shared -fPIC -g v2.c -o libfoo.so
./app
# โ WRONG RESULT: expected GREEN, got 2
Why CRITICAL: the integer value 1 now means YELLOW in v2, but the
compiled app still checks if (c == GREEN) against the old constant 1.
Any stored values, protocol messages, or switch statements using the old
numeric constants silently route to the wrong branch.
Safe redesign¶
Only append new enum values at the end โ never insert them in the middle. Document the enum as append-only, and never renumber existing values.
Real-world example: Protocol Buffers enforces append-only enum values for exactly this reason; inserting values in the middle is a common source of subtle bugs in versioned wire formats.
Cross-tool comparison¶
abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml
echo "exit: $?" # โ 4
Note on abidiff 2.4.0: reports
1 enumerator insertion: 'Color::YELLOW' value '1'plus enumerator changes forGREENandBLUE, exit 4. Semantically breaking because code compiled against v1 uses hardcoded integer values (e.g.if (c == 1)forGREEN) that now meanYELLOW.
References¶
Source files¶
CMakeLists.txtapp.cv1.cv1.hv2.cv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.