Skip to content

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.txt
  • app.c
  • v1.c
  • v1.h
  • v2.c
  • v2.h

See also: Compatibility Catalog · All API_BREAK cases · Category: API Break · Rule: Return pointee gained const · Subject: Function-signature and source-API changes.