Skip to content

Case 54: Used Reserved Field

Field Value
Verdict ๐ŸŸข COMPATIBLE
Category Quality (Compatible)
Platforms Linux, macOS
Flags โ€”
Detected ChangeKinds used_reserved_field
Source files examples/case54_used_reserved_field/

Category: Quality | Verdict: ๐ŸŸข COMPATIBLE

Verdict and consumer impact

v1's Config struct carries __reserved1/__reserved2 placeholder fields. v2 renames them to priority/max_retries at the same offsets, same types โ€” the struct size and layout are byte-identical. Existing binaries that allocate, copy, or read Config keep working unmodified; this is the textbook-correct way to evolve a struct (reserve padding fields upfront, then activate them without an ABI break).

Old/new diff

old/lib.h new/lib.h
int __reserved1; int priority; /* was __reserved1 */
int __reserved2; int max_retries; /* was __reserved2 */

abicheck command

gcc -shared -fPIC -g -include old/lib.h old/lib.c -o libfoo_v1.so
gcc -shared -fPIC -g -include new/lib.h new/lib.c -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so

Expected abicheck finding

Verdict: COMPATIBLE (exit 0)

- used_reserved_field: Reserved field put into use: Config::__reserved1 -> priority
- used_reserved_field: Reserved field put into use: Config::__reserved2 -> max_retries

Minimum evidence

min_evidence: L1 โ€” DWARF's DW_TAG_structure_type member list (name + offset) for both versions is enough for the dedicated reserved-field detector to match __reserved1/__reserved2 renamed to new names at unchanged offsets; no public headers required. (castxml is the documented default AST backend for header-level evidence; clang, via --ast-frontend clang, is a supported alternative when castxml isn't available โ€” neither is needed at this evidence tier.)

Why abicheck catches it

abicheck's reserved-field detector recognizes naming patterns like __reserved, _reserved, __pad, _unused and checks, from DWARF member offsets, whether the renamed field lands at the exact same offset with the same size as the reserved slot it replaces โ€” if so, it's classified COMPATIBLE rather than as a generic field rename/type change.

Runtime failure demonstration

No observable effect on existing binaries โ€” layout is unchanged.

# Build old library + app (app.c uses the v1 struct shape, with __reserved fields)
gcc -shared -fPIC -g -include old/lib.h old/lib.c -o libfoo.so
gcc -g app.c -L. -lfoo -Wl,-rpath,. -o app
./app
# โ†’ flags = 0

# Swap in new library (no recompile)
gcc -shared -fPIC -g -include new/lib.h new/lib.c -o libfoo.so
./app
# โ†’ flags = 0   โ† identical

priority/max_retries simply occupy the bytes the old binary already treats as reserved padding; nothing reads or writes past what v1 expects.

Safe redesign

This case is the safe redesign pattern โ€” reserve fields in the first public release, then activate them later at the same offset instead of growing the struct:

/* v1: reserve slots for future use */
typedef struct {
    int version;
    int __reserved1;  /* -> becomes priority */
    int __reserved2;  /* -> becomes max_retries */
    int flags;
} Config;

Real-world examples: the Linux kernel's struct stat carries __unused/padding fields for exactly this purpose; glibc's pthread_attr_t reserves space for future extensions; Wayland protocol structs use __padding fields the same way.

References


Source files

  • CMakeLists.txt
  • app.c

See also: Examples overview ยท All COMPATIBLE cases ยท Category: Quality (Compatible).