Skip to content

Case 62: Type Field Added (Compatible โ€” Opaque Struct)

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

Category: Type Layout | Verdict: COMPATIBLE

What this case is about

v1 defines Session as an opaque struct with name and timeout fields. v2 adds a priority field at the end. Because callers only use Session* (never allocate, embed, or sizeof the struct), the change is ABI-compatible.

This is the correct design pattern for extensible C APIs: opaque handles + accessor functions allow adding fields without breaking existing consumers.

Real Failure Demo

Severity: COMPATIBLE - NO FAILURE EXPECTED

The struct is opaque to callers, so v2 can grow the private allocation without changing caller layout.

cmake -S examples -B /tmp/abicheck-examples-build -DCMAKE_BUILD_TYPE=Debug
cmake --build /tmp/abicheck-examples-build --target case63_type_field_added_compatible_app case63_type_field_added_compatible_v2

tmp=$(mktemp -d)
cp /tmp/abicheck-examples-build/case63_type_field_added_compatible/app_v1 "$tmp/"
cp /tmp/abicheck-examples-build/case63_type_field_added_compatible/libv2.so "$tmp/libv1.so"
(cd "$tmp" && LD_LIBRARY_PATH=. ./app_v1)
# name = test / timeout = 30

Why this is compatible

  • Callers never see the layout: Session is forward-declared in the header. All allocation is done by session_open() inside the library.
  • Existing field offsets unchanged: name and timeout are at the same offsets. Only a new field is appended.
  • Existing functions unchanged: session_get_name() and session_get_timeout() work identically.

Contrast with case07 (breaking)

Case 07 adds a field to a non-opaque struct that callers sizeof and embed โ€” that's breaking. This case demonstrates the safe pattern.

What abicheck detects

  • TYPE_FIELD_ADDED: A new field was added to the struct.
  • FUNC_ADDED: session_get_priority() is a new symbol.

Overall verdict: COMPATIBLE

How to reproduce

gcc -shared -fPIC -g bad.c  -include bad.h  -o libbad.so
gcc -shared -fPIC -g good.c -include good.h -o libgood.so

python3 -m abicheck.cli dump libbad.so  -o /tmp/v1.json
python3 -m abicheck.cli dump libgood.so -o /tmp/v2.json
python3 -m abicheck.cli compare /tmp/v1.json /tmp/v2.json
# โ†’ COMPATIBLE: TYPE_FIELD_ADDED, FUNC_ADDED

Design pattern

/* PUBLIC HEADER โ€” opaque pointer */
typedef struct Widget Widget;
Widget* widget_new(void);
void widget_free(Widget *w);

/* PRIVATE IMPLEMENTATION โ€” can grow freely */
struct Widget {
    int x, y;
    int new_field;  /* โ† safe to add */
};

Real-world examples

  • OpenSSL: All major types (SSL, EVP_MD_CTX, etc.) are opaque since 1.1.0
  • libcurl: CURL * handle is fully opaque
  • SQLite: sqlite3 * is opaque

References


Source files

  • CMakeLists.txt
  • app.c
  • bad.c
  • bad.h
  • good.c
  • good.h

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