Case 186: C API Pointee const-Qualification Is ABI-Neutral¶
| Field | Value |
|---|---|
| Verdict | ✅ NO_CHANGE |
| Category | No Change |
| Platforms | Linux |
| Flags | — |
Detected ChangeKinds |
— |
| Source files | examples/case186_c_api_pointee_const_abi_neutral/ |
Category: No Change | Verdict: ✅ NO_CHANGE
Verdict and consumer impact¶
send_buffer()'s parameter changes from char * to const char *. The
pointer itself is still one machine word, passed the same way, with the
same calling convention — only the pointee's mutability contract tightened
(the callee now promises not to write through the pointer). Every existing
call site — whether it passes a mutable or already-const buffer — still
compiles and links unchanged against v2. No consumer action is required.
Old/new diff¶
| v1.h | v2.h |
|---|---|
void send_buffer(char *data); |
void send_buffer(const char *data); |
abicheck command¶
gcc -shared -fPIC -g v1.c -o libv1.so
gcc -shared -fPIC -g v2.c -o libv2.so
abicheck compare libv1.so libv2.so --header old=v1.h --header new=v2.h --ast-frontend clang
Expected abicheck finding¶
func_params_changed is expected not to fire here (it would for an
ordinary parameter-type change) — the suppression is the point of the case.
Minimum evidence¶
min_evidence: L2 — a mechanical type-spelling diff would need only DWARF
to see char * vs const char * as differing strings and misreport a
break; the public header AST is what lets abicheck recognize the top-level
*-plus-const-only shape and suppress the finding correctly rather than
merely downgrade it. castxml is the documented default backend for this
evidence layer; clang (--ast-frontend clang, used above) is a supported
alternative AST frontend for hosts without castxml installed.
Why abicheck catches it¶
abicheck/name_classification.py's cv_qualifiers_only_differ() recognizes
a type-pair shape where both sides have a top-level */& and differ only
by const/volatile on or behind it. The call site in diff_symbols.py
(_params_differ) skips emitting a finding entirely when it fires — this is
fully suppressed, not merely downgraded to a risk tier — because a char *
argument implicitly converts to const char * at every call site, so no
caller can fail to link or misbehave.
Runtime failure demonstration¶
Severity: none — verified no observable effect.
# Build old library + app
gcc -shared -fPIC -g v1.c -o libv1.so
gcc -g app.c -L. -lv1 -Wl,-rpath,. -o app
./app
# → sent 5 bytes
# Swap in new library (no recompile)
gcc -shared -fPIC -g v2.c -o libv1.so
./app
# → sent 5 bytes (identical output, no crash, no misread argument)
The pointer is still passed in the same register with the same width; the callee's added promise not to write through it changes nothing observable to a caller compiled against the old signature.
Safe redesign¶
None needed — this is the safe pattern. Adding const to a public struct
field, by contrast, is not automatically safe: code that writes through
the field (buf.data[0] = 'x') would stop compiling against a
const-qualified field, a real source-level break despite identical binary
layout — that hazard is exactly why this case is scoped to a function
parameter rather than a struct field.
Real-world example: Wayland's wl_display accessor functions picked up
pointee const on their parameters between releases without an actual ABI
break, and conda-forge's libuv 1.5x packaging campaign hit exactly this
false-positive class — the motivating case for this suppression.
Cross-tool comparison¶
A naive AST- or symbol-spelling diff (including some abidiff
configurations) reports this as a signature change requiring investigation,
since char * and const char * are different type strings; abicheck's
header-aware cv_qualifiers_only_differ() check is what tells the two
apart from a real pointee-type change (see
case46_pointer_chain_type_change
for the non-suppressed baseline).
References¶
tests/test_const_pointer_abi_neutral.py— unit-level equivalenttests/test_libuv_private_type_churn.py— the struct-field negative case
Source files¶
CMakeLists.txtapp.cv1.cv1.hv2.cv2.h
See also: Examples overview · All NO_CHANGE cases · Category: No Change.