case156_public_macro_removed โ Public macro removed¶
| Field | Value |
|---|---|
| Verdict | ๐ API_BREAK |
| Category | API Break |
| Platforms | Linux |
| Flags | API break |
Detected ChangeKinds |
public_macro_removed |
| Source files | examples/case156_public_macro_removed/ |
Category: API Break | Verdict: ๐ API_BREAK
Verdict and consumer impact¶
The public header macro DEMO_MAX_ITEMS is present in v1's source-replay
surface and gone from v2. Macros are a preprocessor construct โ they never
reach the binary โ so removing one changes nothing an artifact-only check
can see, but any consumer source that referenced DEMO_MAX_ITEMS (a
constant, a feature guard, a function-like macro invocation) fails to
compile against v2's headers. This is a source/API break: recompilation
against v2 is not just required, it's impossible without a code change.
Old/new diff¶
| include/demo/config.h โ v1 | include/demo/config.h โ v2 |
|---|---|
#define DEMO_MAX_ITEMS 64 |
(removed) |
The exported binary symbol (demo::keep) is unchanged between v1 and v2;
only the header-level macro surface differs.
abicheck command¶
The case ships old.json/new.json as hand-built SourceAbiSurface
fixtures (the normalized L4 model dump --sources would produce from a real
source-replay pass) rather than compiled binaries, so reproducing the
finding means embedding each side's fixture into a snapshot's
build_source field โ exactly what dump --sources does internally โ and
comparing the two:
python3 - <<'PY'
import json
from pathlib import Path
from abicheck.model import AbiSnapshot
from abicheck.buildsource.pack import BuildSourcePack
from abicheck.buildsource.source_abi import SourceAbiSurface
from abicheck.serialization import save_snapshot
for side in ("old", "new"):
d = json.loads(Path(f"{side}.json").read_text())
snap = AbiSnapshot(library="libdemo.so", version="1")
snap.build_source = BuildSourcePack(root=Path(""), source_abi=SourceAbiSurface.from_dict(d))
save_snapshot(snap, f"{side}.abi.json")
PY
abicheck compare old.abi.json new.abi.json
Expected abicheck finding¶
Verdict: API_BREAK (exit 2)
Source-Level Breaks:
- public_macro_removed: Public macro 'DEMO_MAX_ITEMS' was removed from the headers.
('64' -> '') โ include/demo/config.h:12 [L4_SOURCE_ABI]
> Source that referenced it no longer compiles; there is no binary
footprint, so only source replay sees it.
Minimum evidence¶
min_evidence: L4 โ only the per-TU source-replay surface records public
macros. Macros never reach the binary, so no artifact layer (L0 symbols, L1
DWARF, L2 header AST as parsed for types/decls) sees the removal; L4 source
replay is the floor.
Why abicheck catches it¶
abicheck compare reads each side's normalized SourceAbiSurface's
reachable_source_surface.macros (as embedded by dump --sources, or
supplied out-of-band via --old/new-sources) and diffs the macro set
directly โ the same diff_source_abi() routine tests/test_l3l4l5_examples.py
exercises against the committed fixtures. Per ADR-028 D3 a source-evidence
finding never decides a shipped-ABI break on its own, but public_macro_removed
is registered as an API_BREAK_KINDS default verdict (a source-level break),
not a risk signal โ the compile failure it causes is deterministic, not
contingent on a further artifact diff.
Runtime failure demonstration¶
There's no compiled app.c consumer for this case โ the failure happens at
compile time, not runtime, so a "swap the .so and observe a crash" demo
doesn't apply here. Picture a downstream package that does
#if DEMO_MAX_ITEMS > 32 or simply uses DEMO_MAX_ITEMS as an array bound;
after upgrading to v2's headers, that translation unit fails to compile with
'DEMO_MAX_ITEMS' undeclared. This is exactly what a CI job running source
replay (L4) over a consumer's include path โ or over the library's own
public headers on each PR โ would catch before the header ships, well
before any consumer attempts to build against it.
Safe redesign¶
Keep a compatible macro in place (optionally #define-forwarding to a
replacement constant or enum), or document the removal with a migration
path and a deprecation window before dropping it โ the same discipline
applied to removing a public symbol, since the compile-time contract is
just as binding on consumers.
Cross-tool comparison¶
abidiff/abi-compliance-checker operate on built binaries and their debug
info; macros are stripped by the preprocessor before either format exists,
so neither tool can see this removal under any invocation โ there is
nothing to run. Only the L4 source-replay layer that abicheck reads
directly (or the header AST as parsed by a full C/C++ frontend) preserves
the macro-removal fact at all.
Source files¶
new.jsonold.json
See also: Examples overview ยท All API_BREAK cases ยท Category: API Break.