Repository navigation
verify: Follow the body of a choose in a spec's reads and postcondition checks - #43
Merged
Merged
Conversation
This was referenced Oct 20, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A spec's checks did not look inside the body of a
cppverify::chooseit applies, which cost two things.A reads clause over a choice failed, although it holds:
and every proof that used the frame was
Unresolvedwith reasonspec.reads. The reads check treated the lifted choice as a heap-reading callee without a reads clause.A postcondition proved by induction could not use the hypothesis at a call inside a choose:
This stayed
Unresolved(spec.fuel) after 35 s.SpecBodyCollector, which gathers the loads and applications of a spec body for its reads, postcondition, and termination checks, stopped at the choice's application.The change: at the application of a choice,
SpecBodyCollectorwalks the choice's witness body (choiceWitness, as the recursion check already does since the choose recursion fix), at the application's arguments and with its binder as an enclosing quantifier. The collector owns the witness, since its sites point into it. The reads check then checks every load of the body against the declared cells at every binder value, as Dafny checks the reads of a let-such-that, and skips the choice callee itself, whose loads are now sites of their own. A choice whose witness cannot be built makes the check unsupported rather than skipping it. The postcondition checks get the induction hypothesis and the other specs' postconditions at the applications inside the body.Docs: the reads section of the language reference shows a choose inside a frame and one that reads past it (
index_past, with its counterexample). ch13 showsbitand why the hypothesis proves it. The release notes get an entry under "Failures that were not real".Testing:
Verify/suite/choose_reads.cppcovers the reads ofindex_of(verified) andindex_past(fails atn = 0); a frame kept across a write elsewhere, for a hidden spec and for a recursive one; and a write inside the frame, which fails with a counterexample. Inchoose_axioms.cpp,bit_unblockedis now verified, in 0.4 s. Both fail on main. Two mutations each failchoose_reads.cpp:index_past's reads check verify. That is the unsound one.index_of.Checked: Verify suite 424 passed, 1 unsupported; VerifyTests 60 passed, 1 skipped (encoding parity, which needs archives). Together with #44, in a build with assertions, the Verify suite passes (425, 1 unsupported). A verdict differential against main over 431 Verify sources (the ULEB128 exhaustive decoder left out: it has no choose, and the change runs only at a choice's application) changes only the two test files above.
With #44 as well, a write elsewhere also keeps a non-recursive spec over a choice, which is inlined and so has no application left for its frame to apply to.