case154_lto_mode_flip โ LTO mode flip (-flto)¶
| Field | Value |
|---|---|
| Verdict | ๐ก COMPATIBLE_WITH_RISK |
| Category | Risk |
| Platforms | Linux |
| Flags | Bad practice |
Detected ChangeKinds |
lto_mode_changed |
| Source files | examples/case154_lto_mode_flip/ |
Category: Risk | Verdict: ๐ก COMPATIBLE_WITH_RISK
Verdict and consumer impact¶
v1 and v2 share identical source; v1 was built without LTO, v2 with
-flto. Link-time optimization changes cross-TU inlining and can
devirtualize or drop vtable/typeinfo emission the linker would otherwise
keep, so the emitted symbol set and the inlined bodies of public inline
functions can differ from a non-LTO build of the same source โ even though
nothing in the source changed. A consumer relying on a symbol the old
non-LTO build happened to export may find it gone, or may link against a
devirtualized call path the source no longer literally expresses.
Old/new diff¶
| Build flags | v1 | v2 |
|---|---|---|
| LTO | off | -flto |
Source is identical between v1 and v2; only this compile/link option differs.
abicheck command¶
The case ships old.json/new.json as hand-built BuildEvidence fixtures
(the normalized L3 model dump --build-info/--sources would produce from a
real build) rather than compiled binaries, so reproducing the finding means
embedding each side's fixture into a snapshot's build_source field โ
exactly what dump --build-info 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.build_evidence import BuildEvidence
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(""), build_evidence=BuildEvidence.from_dict(d))
save_snapshot(snap, f"{side}.abi.json")
PY
abicheck compare old.abi.json new.abi.json
Expected abicheck finding¶
Verdict: COMPATIBLE_WITH_RISK (exit 0)
Deployment Risk Changes:
- lto_mode_changed: Runtime-model option 'lto' changed: 'off' -> 'on'.
> May not be link- or runtime-compatible across consumers; the artifact
diff confirms any concrete break.
Minimum evidence¶
min_evidence: L3 โ the compile/link options themselves carry the fact.
The change is a property of how the library was built, not something
visible in a diff of one artifact: symbol-only checks that happen to see a
stable export set on both sides would miss the inlining/emission
differences LTO can introduce elsewhere.
Why abicheck catches it¶
abicheck compare reads each side's normalized BuildEvidence.build_options
(as embedded by dump --build-info/--sources, or supplied out-of-band via
--old/new-build-info) and diffs the lto option directly โ the same
diff_build_evidence() routine tests/test_l3l4l5_examples.py exercises
against the committed fixtures. Per ADR-028 D3 this build-evidence finding
never decides a shipped-ABI break on its own โ it flags the elevated risk
and localizes the cause; an artifact diff of the actual compiled symbol
sets and layouts is what would confirm a concrete break.
Runtime failure demonstration¶
There's no compiled app.c consumer for this case โ the failure mode is a
build-pipeline one, not a single process crash. Picture a release job that
enables -flto for a performance-tuned production build while a debug or
sanitizer build of the same library stays non-LTO: if a consumer links
against whichever build happens to be on disk, it can pick up an ABI that
differs in exactly the symbols LTO devirtualized or inlined away. A CI job
that diffs captured build options between build variants โ not just between
releases โ is exactly what would catch this before both variants ship.
Safe redesign¶
Prefer a single LTO policy across the library and its consumers. If public inline functions or polymorphic (vtable-bearing) types are involved, rebuild consumers against the matching LTO setting rather than mixing LTO and non-LTO builds of interdependent binaries.
Cross-tool comparison¶
abidiff/abi-compliance-checker compare built binaries (symbols + DWARF);
neither reads compile/link options, so two builds from identical source
under different -flto settings produce no diff for either tool unless LTO
actually changed the emitted symbol set or layout in the compiled output
(at which point it's that resulting change they'd catch, not the flag
itself). Only the L3 build-evidence layer that abicheck reads directly
localizes the cause to the LTO flag flip.
Source files¶
new.jsonold.json
See also: Examples overview ยท All COMPATIBLE_WITH_RISK cases ยท Category: Risk.