Case 158: Public Typedef Removed¶
| Field | Value |
|---|---|
| Verdict | ๐ API_BREAK |
| Category | API Break |
| Platforms | Linux |
| Flags | API break |
Detected ChangeKinds |
public_typedef_removed |
| Source files | examples/case158_public_typedef_removed/ |
Category: Build/Source Evidence (L4) | Verdict: ๐ API_BREAK
This case ships a hand-built pair of evidence-model fixtures (
old.json+new.jsonโSourceAbiSurfacedumps) instead of compilablev1/v2sources, so the corpus is also validated compiler-free bytests/test_l3l4l5_examples.py(which callsdiff_source_abion the same two files directly). Seescripts/gen_l3l4l5_examples.pyfor how they were generated.
Verdict and consumer impact¶
The public typedef demo::handle_t (an alias for int) is present in v1
and removed in v2. A bare typedef emits no symbol and has no size or layout
of its own, so the shipped .so is unaffected โ this is not a binary
compatibility problem. It is a source compatibility problem: any consumer
source that names the alias (a variable declaration, a cast, a template
argument) fails to compile against the v2 headers.
Old/new diff¶
| v1 source-surface fact | v2 source-surface fact |
|---|---|
include/demo/handle.h:8 โ typedef demo::handle_t = int, public header, type hash sha256:h1 |
(removed โ no matching types entry) |
include/demo/keep.h:3 โ demo::keep(), public header (unrelated, unchanged) |
include/demo/keep.h:3 โ demo::keep(), unchanged |
abicheck command¶
# old.json / new.json here are this case's committed L4 source-ABI-replay
# fixtures (SourceAbiSurface dumps, as produced by source-replay extraction
# over a real source checkout) -- not AbiSnapshot files, so they're supplied
# to abicheck as an out-of-band --build-info pack rather than as compare's
# positional inputs.
mkdir -p pack_old/source pack_new/source
echo '{"build_source_pack_version": 1}' > pack_old/manifest.json
echo '{"build_source_pack_version": 1}' > pack_new/manifest.json
cp old.json pack_old/source/source_abi.json
cp new.json pack_new/source/source_abi.json
# Nothing changed at the binary/header level between v1 and v2, so a pair of
# otherwise-empty snapshots stands in for the (unchanged) artifact side.
python3 -c "
from abicheck.model import AbiSnapshot
from abicheck.serialization import save_snapshot
save_snapshot(AbiSnapshot(library='libdemo.so', version='1.0'), 'empty_old.json')
save_snapshot(AbiSnapshot(library='libdemo.so', version='2.0'), 'empty_new.json')
"
abicheck compare empty_old.json empty_new.json \
--build-info old=pack_old --build-info new=pack_new
Expected abicheck finding¶
Verdict: API_BREAK (exit 2)
- public_typedef_removed: Public typedef 'demo::handle_t' removed
(include/demo/handle.h:8 [L4_SOURCE_ABI])
> Emitted no symbol of its own, so only source replay sees the
removal; source that named the alias no longer compiles.
Minimum evidence¶
min_evidence: L4 โ a typedef contributes nothing to the exported-symbol
table (L0) or DWARF (L1), and header-AST scoping alone (L2) records the
public surface but isn't what this corpus's diff runs against. Only source
replay's per-TU surface, built by a source extractor (clang, castxml, or the
Android header-ABI dumper) over the real translation units, keeps typedefs
as first-class facts โ which is what lets diff_source_abi see one vanish.
Why abicheck catches it¶
SourceAbiSurface.reachable_source_surface.types carries one entry per
public type-level declaration reachable from the linked library โ including
typedefs, keyed by qualified name with the aliased type recorded as value.
diff_source_abi diffs the two surfaces' types sets directly and reports
public_typedef_removed for a typedef entry present in v1 and absent in v2.
(The sibling public_typedef_target_changed kind covers the case where the
alias is kept but re-pointed to a different underlying type.)
Build/deployment scenario¶
This is what a CI job replaying two releases' public headers through a
source extractor (or ingesting a captured abicheck_inputs/ pack from the
build) would see the moment a public alias is dropped: the .so's symbol
table and DWARF don't move at all, so a pipeline gating only on a
binary/DWARF abicheck compare would ship this as clean. Only a scan that
also collects L4 source-replay evidence over the public header tree catches
that downstream code declaring a demo::handle_t variable, or casting to
one, fails to compile against the new release.
Safe redesign¶
Keep the typedef as an alias for its replacement instead of deleting it
outright โ using handle_t = new_handle_t; costs nothing at the ABI level
and keeps existing consumer source compiling. Remove the alias only after a
documented deprecation window, or provide a replacement name and update
consumers first.
Real-world example: this is exactly how POSIX/glibc handle typedef
churn โ e.g. width-sensitive _t aliases stay pointed at a new underlying
type for years instead of being deleted outright, precisely to avoid this
break.
Cross-tool comparison¶
Neither abidiff nor abi-compliance-checker have an operand here: both
tools diff compiled binaries (plus optional DWARF/headers), and a bare
typedef removal is โ by construction โ invisible at that layer; the whole
reason it needs L4 source-replay evidence is that no built binary or
header-AST-only diff can see it. There's no .so/abidw XML pair to hand
either tool for this case.
Source files¶
new.jsonold.json
See also: Examples overview ยท All API_BREAK cases ยท Category: API Break.