Skip to content

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

abicheck scan snapshot.abi.json

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).