Skip to content

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

abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml

References


Source files

  • CMakeLists.txt
  • app.c
  • v1.c
  • v1.h
  • v2.c
  • v2.h

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