Case 211: Return Pointee Gained const¶
| Field | Value |
|---|---|
| Verdict | 🟠 API_BREAK |
| Category | API Break |
| Classification | Rule |
| Platforms | Linux |
| Flags | API break |
Detected ChangeKinds |
func_return_pointee_qualifier_added |
| Source files | catalog/cases/case211_return_pointee_gained_const/ |
| Rule family | return-pointee-gained-const |
| Subject | Function-signature and source-API changes |
Category: Source API | Verdict: 🟠 API_BREAK
Verdict and consumer impact¶
get_name()'s return type changes from char * to const char *. Nothing
binary changes — the same pointer comes back in the same register — so every
prebuilt consumer keeps working after the library swap. Source compatibility
does break: a caller that stores the result in a plain char *, which v1's
declaration permits, no longer compiles against v2. In C++ that is a hard
error; in C it is a constraint violation (GCC 14+ rejects it by default,
GCC 13 and Clang warn and reject it under -Werror).
It is the return-value counterpart of
case186_c_api_pointee_const_abi_neutral.
There, a parameter pointee gaining const makes the callee promise more and
every direct call still compiles (a conditional risk). Here the qualifier
lands on what the caller receives, so the ordinary call pattern itself
breaks — an unconditional API_BREAK.
Old/new diff¶
| v1.h | v2.h |
|---|---|
char *get_name(void); |
const char *get_name(void); |
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
Expected abicheck finding¶
Verdict: API_BREAK (exit 2)
func_return_pointee_qualifier_added: Return pointee qualifier added: get_name: char* → const char*
func_return_changed must not appear: the return type's binary
representation did not change. Verified identically with
ABICHECK_AST_FRONTEND=castxml and ABICHECK_AST_FRONTEND=clang.
Minimum evidence¶
min_evidence: L2 — C symbols carry no type, so the export table (L0) cannot
see it, and abicheck declines to read a pointee-qualifier change off DWARF
alone; the public header AST is the evidence tier the finding rests on.
Why abicheck catches it¶
The header backends record each function's return type with its pointee
qualifiers. The return-type comparison separates a change confined to the
first-level pointee's const/volatile from a real change of the returned
type: a gained qualifier is func_return_pointee_qualifier_added
(API_BREAK), a dropped one func_return_pointee_qualifier_removed, and
neither is folded into func_return_changed (BREAKING).
Runtime failure demonstration¶
Severity: none at runtime; build failure on recompile.
gcc -shared -fPIC -g v1.c -o libv1.so
gcc -g app.c -L. -lv1 -Wl,-rpath,'$ORIGIN' -o app
./app # name = libname exit 0
gcc -shared -fPIC -g v2.c -o libv1.so # swap in v2, no recompile
./app # name = libname exit 0
The break shows up when the consumer is rebuilt against v2. The catalog's
source_smoke states it as a hard check under -std=c11:
#include "v2.h"
_Static_assert(__builtin_types_compatible_p(__typeof__(get_name()), char *),
"get_name() no longer returns char *: char *p = get_name(); breaks");
int main(void) { char *p = get_name(); return p != 0 ? 0 : 1; }
It compiles against v1.h and fails against v2.h with GCC and Clang.
Safe redesign¶
Add a new accessor that returns const char * (get_name_const()) and keep
the old one's signature, or make the change at a major version with the
source break documented.
Cross-tool comparison¶
abidiff reports a return-type subtype change (char* → const char*)
without separating binary from source impact; ABICC reports it in its
source-compatibility report only.
Source files¶
CMakeLists.txtapp.cv1.cv1.hv2.cv2.h
See also: Compatibility Catalog · All API_BREAK cases · Category: API Break · Rule: Return pointee gained const · Subject: Function-signature and source-API changes.