Skip to content

verify: Compare a callee footprint with the caller's frame by its cells - #52

Merged
SwayamInSync merged 8 commits into
mainfrom
fix/footprint-containment
Oct 21, 2026
Merged

SwayamInSync merged 8 commits into
mainfrom
fix/footprint-containment

Conversation

@SwayamInSync

Copy link
Copy Markdown
Owner

A call failed its frame check, with a counterexample, even when the callee wrote only cells the caller may write. This happened in 0.1.1 and on main:

void set(int *p, int v)
  cppverify::pre(p != nullptr)
  cppverify::modifies(*p)
  cppverify::post(*p == v)
{
  *p = v;
}

void set_third(int *a)
  cppverify::pre(cppverify::valid(a, 4))
  cppverify::modifies(a[2])
{
  set(a + 2, 5);        // main: verification failed [frame]
}

void set_through_copy(int *a)
  cppverify::pre(cppverify::valid(a, 4))
  cppverify::modifies(*a)
{
  int *q = a + 2;
  set(q, 5);            // main: verification failed [frame]
}

Cause

footprintWithin (Passivize.cpp) decides whether a callee footprint lies in a caller footprint. It has two gaps:

  • Caller cell, callee region: it returned false, even though calleeFootprintBytes already computes a region of one scalar object as one cell.
  • Caller region: it compared the two pointers' bases by name (samePointerRegion: a == q). A callee footprint reached through a copy of a pointer into the region's object therefore never fit.

A store already follows the semantic rule. In the function's own frame check, a store through any pointer is inside a region footprint when its address lies in the region parameter's entry object.

Fix

The call check now follows the same rule as stores:

  • Caller cell: a callee region that is one cell lies in the caller's cell when the two are the same cell.
  • Caller region on a parameter: a callee footprint reached through a pointer with another base lies in the region when its cells lie in that parameter's entry object. A one-cell region counts as its cell, and a range or an extent counts as its bytes.

A footprint with the same base as the caller's region produces exactly the query it did before. A call that failed before can now verify only when its cells provably lie in the caller's frame. The test's false twins check this:

  • the neighboring cell;
  • a copy into another parameter's object, including one placed above a by the precondition;
  • a choice whose b side is outside the frame;
  • a range that runs past the caller's cell;
  • a callee whose modifies range is wider than its extent, from a + 3;
  • a trusted result one cell before the object.

This is also the frame half of B3a-2. Passing &r->head to set under modifies(r->head) needs this check.

Tests

suite/frame_call_objects.cpp contains:

  • Proofs: a cell for a one-object region; an element for a one-object region; a copy for a region; a copy for a range; a choice between two writable objects; and an empty range elsewhere, which writes nothing.
  • Failures: each false twin above, each failing its frame check at the call, with c = false for the choice.

On main every function in the file except the callees fails.

Docs

  • pointers.rst: a checked example and its verdicts.
  • Rule text: the same rule in book chapters 14 and 17, DESIGN.md, and ARCHITECTURE.md.
  • Release notes: an entry under "Failures that were not real".

Checked

On main at de4972d:

@SwayamInSync
SwayamInSync merged commit 573c171 into main Oct 21, 2026
14 checks passed
@SwayamInSync
SwayamInSync deleted the fix/footprint-containment branch October 21, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant