Case 147: Depth Ladder โ the Same Input Answered at Increasing Depth¶
| Field | Value |
|---|---|
| Verdict | ๐ข COMPATIBLE |
| Category | Quality (Compatible) |
| Platforms | Linux |
| Flags | Bad practice |
Detected ChangeKinds |
private_header_leak |
| Source files | examples/case147_scan_depth_ladder/ |
Category: Quality (Audit) | Verdict: ๐ข COMPATIBLE (bad practice)
Verdict and consumer impact¶
Single-release audit: one build's evidence checked against itself, no
baseline. abicheck's verdict is COMPATIBLE, but the audit flags an
advisory finding: connect() is a public function that takes
detail::SessionState&, a type declared only in a private header โ the same
private_header_leak shape as case144, but this case exists to demonstrate
how much evidence abicheck needed to prove it. ADR-035's honest-coverage
promise is that a scan says exactly what each depth proved and what it
could not, rather than silently upgrading a hint into a confirmed finding.
This case is the legibility anchor for that promise: the same input, read at
increasing evidence depth.
What this snapshot contains¶
snapshot.abi.json is a single, hand-built AbiSnapshot for one build of
libdemo.so. Unlike case144/146, it carries both the L2 header
provenance and a baked-in L5 source graph, so it represents what a live
--depth source scan would have already collected:
| Source in the snapshot | What it records |
|---|---|
| Binary export table (L0) | _Z7connectv (connect) exported |
| Public-header AST (L2) | connect(detail::SessionState &s); the detail::SessionState struct carries origin: private_header |
| Source graph (L5) | corroborates the reference, adding source_index as a second provider alongside public_header_ast |
abicheck command¶
Expected abicheck finding¶
Verdict: COMPATIBLE (exit 0)
crosscheck:private_header_leak present public API โ private-header provenance:
1 public declaration(s) exposing one of 1 private type(s)
ABI-hygiene catalog (intra-version, advisory)
[warning] private_header_leak: 1
private_header_leak's provider list on this snapshot is
["public_header_ast", "source_index"] (checked with the same
crosscheck_surface() helper tests/test_g20_catalog.py asserts against) โ
the source_index entry is what marks this as the L5-corroborated case,
distinct from case144's ["public_header_ast"]-only leak.
Minimum evidence¶
min_evidence: L2 โ a public-header AST reference to a private-header type
is already enough to raise the finding; the L5 source graph baked into this
fixture corroborates it with a resolved call/reference edge, it isn't
required to produce the finding in the first place. That's the point of the
ladder: L2 alone already proves the leak here (unlike a case where only a
lexical pattern hints at it without AST confirmation).
Why abicheck catches it โ and what depth actually changes here¶
Because this is a committed snapshot fixture, not a live binary/source
tree, the --depth flag controls how much new evidence abicheck would
collect from --sources/--build-info โ evidence this fixture doesn't need
collected because it's already baked in. Verified directly against this
file:
abicheck scan snapshot.abi.json --depth headers # exit 0, same finding
abicheck scan snapshot.abi.json --depth binary # exit 0, same finding
abicheck scan snapshot.abi.json # exit 0, same finding (auto -> depth=source)
abicheck scan snapshot.abi.json --depth source # errors: needs --sources/--build-info
The first three all report crosscheck:private_header_leak present with the
identical detail line โ because the L2 header AST alone already carries the
detail::SessionState โ private-header fact, pinning a shallower --depth
doesn't hide it here. Pinning --depth source explicitly fails against
this fixture, since that pin means "collect fresh L3โL5 evidence" and there
is no real source tree to collect it from โ --sources <tree> is required.
Against a live binary + real source tree (not a committed fixture), the
ladder plays out as designed: --depth headers gives only the AST-level
hint, and --depth source is what adds the resolved source-graph
corroboration that upgrades it to a confirmed cross-check.
Why this matters for a real release¶
A scan that silently reports "no leak found" because it only had shallow
evidence would be worse than one that says "not checked at this depth" โ
the honest-coverage contract is what lets a CI policy decide how much
evidence to require before trusting a COMPATIBLE result. Here, L2 already
proves the leak; a project could still choose to require L5 corroboration
before gating on it, and the coverage report is what makes that choice
possible instead of guesswork.
Safe redesign¶
Same fix as a private-header leak generally: give detail::SessionState an
opaque handle so the public signature no longer names a private-header type,
or install the header that defines it.
Cross-tool comparison¶
private_header_leak is a cross-source check unique to abicheck's audit
mode โ it reconciles a public declaration against the provenance of the
type it references, optionally corroborated by a source graph, within the
same build. This depth-ladder framing (evidence tiers, --depth dial,
honest per-layer coverage reporting) has no equivalent in
abidiff/abi-compliance-checker, which only diff two ABI dumps against
each other at a single, fixed evidence level.
Source files¶
snapshot.abi.json
See also: Examples overview ยท All COMPATIBLE cases ยท Category: Quality (Compatible).