Case 70: Flexible Array Member Element Type Changed¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS, Windows |
| Flags | ABI break, API break |
Detected ChangeKinds |
flexible_array_member_changed, func_return_changed |
| Source files | examples/case70_flexible_array_member_changed/ |
Category: Type Layout | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
The flexible array member (FAM) Packet::data[] changes element type from
float (4 bytes) to double (8 bytes). sizeof(Packet) itself is
unchanged โ a FAM has no static size โ so callers that allocated
sizeof(Packet) + count * sizeof(float) now have half the space a
double[] needs, and every data[i] access reads at the wrong byte
offset. packet_sum()'s return type also changes from float to
double. Any binary using Packet directly needs recompilation.
Old/new diff¶
| v1.h | v2.h |
|---|---|
float data[]; |
double data[]; |
float packet_sum(const struct Packet *p); |
double packet_sum(const struct Packet *p); |
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)
- struct_field_type_changed: Field type changed: Packet::data float[](0B) -> double[](0B)
> Field type changed in binary; old code misinterprets the field data.
Affected symbols: packet_create, packet_free, packet_sum
- func_return_changed: Return type changed: packet_sum (float -> double)
> Callers expect the old return type layout in registers/stack;
misinterpretation causes data corruption.
Quality:
- flexible_array_member_changed: Flexible array member element type changed:
Packet::data (float[] -> double[])
Minimum evidence¶
min_evidence: L1 โ DWARF's array-type DIE for the trailing member records
its element type for both versions; sizeof(Packet) stays identical since
a FAM has zero static size, so only DWARF inspection of the tail element
type (not the struct's overall size) reveals the break โ -g is enough,
no public headers required.
Why abicheck catches it¶
abicheck reads the FAM's DWARF element-type DIE directly (rather than
relying on the struct's static size, which doesn't change for a FAM), and
compares it between versions; it also compares packet_sum's DWARF return
type, since register width for the return value shifted from float to
double.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: compile app against v1, swap in v2 .so without recompile.
# Build v1 and app
gcc -shared -fPIC -g v1.c -o libpacket.so
gcc -g app.c -L. -lpacket -Wl,-rpath,. -o app -lm
./app
# โ packet_sum = 10.0
# โ Expected: 10.0
# Swap in v2 (FAM element type changed, no recompile)
gcc -shared -fPIC -g v2.c -o libpacket.so
./app
# โ packet_sum = 0.0
# โ Expected: 10.0
# โ WRONG RESULT: flexible-array element type changed
Why CRITICAL: packet_create(1, 4) now allocates space for 4 double
elements (32 bytes of FAM data), written as doubles by v2's packet_create.
The app's own read path expects float elements and the old packet_sum
return-type convention (an FP register width for float, not double);
the mismatch produces a silently wrong sum instead of the expected 10.0,
with no crash or warning.
Safe redesign¶
Use an opaque allocation API and accessor functions instead of exposing the FAM directly:
/* Safe: opaque packet โ element type is hidden */
typedef struct Packet Packet;
Packet *packet_create(unsigned int id, unsigned int count);
float packet_get(const Packet *p, unsigned int index); /* accessor */
If the FAM must stay public, freeze the element type and introduce a new struct for the new element type instead of changing the existing one.
Real-world example: flexible array members are common in network
protocol implementations (packet buffers) and database engines
(variable-length records) โ the Linux kernel's struct sk_buff and
PostgreSQL's varlena types use this pattern extensively, and changing a
FAM's element type is a subtle break because sizeof(T) stays the same;
only DWARF inspection of the trailing array's element type reveals it.
Cross-tool comparison¶
References¶
Source files¶
CMakeLists.txtapp.cv1.cv1.hv2.cv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.