Repository navigation
E4a: counterexamples after a loop noted, and confirmed by unrolling - #94
Merged
Merged
Conversation
…ps, and name an unmeasured loop beside a failure.
… proved by unrolling, and beside an unmeasured loop.
This was referenced Oct 27, 2026
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 failure whose counterexample passed through a loop looked like any other, though its state there came from the loop's invariants, and no execution need reach it. The plan's first example (R19) is the first loop most users write:
On the base (#93) it fails with
c = 0after the loop and nothing more. Now:and a failure an execution reaches says within how many iterations, with that execution (a division by
3 - cin the loop's body,n <= 5):A failure of a function whose loop has no
cppverify::decreasesalso names that loop: with a proof the function would still beUnresolved(decreases.missing), which the base never said beside a failure.Stacked on #93. Plan item E4, its first half (G5: the counterexample's kind and its confirmation by unrolling, and
decreases.missingbeside a failure); decision Q12 taken as recommended, (a): the verdict staysverification failed, and the notes carry the difference. The other half, every failing obligation reported (G6), follows.Design
SpecInliner::prepareFunction, the UB checks), each bound from 0 to--confirm-bound(default 10,0skips) unrolled, passivized against the interface map, and solved obligation by obligation (Z3VerifyBackend::verifyObligations), as CBMC and Kani check unrolled programs (the plan's G5). The copies of the failed check are the obligations of its kind at its source location: one per iteration for a check in a loop's body. A failing copy is an execution within that many iterations (callees by their contracts), and its counterexample is noted; copies that hold where every loop's unwinding is proved hold for every execution; copies that hold short of that go on to the next bound; anything unresolved ends the search without a note. Unrolling keeps no loop invariant and no measure, so a failed invariant or measure has no copies and gets the first note only. The whole search gets one query timeout.VerifyDiagnostic::Notes, printed after the error (and after Clang's instantiation note, when there is one) asnote:lines, and in JSON asnotes.Tests
suite/inductive_counterexamples.cpp(new), in order with-NEXT: a correct function whose loop lacks an invariant (unrolling proves the check), a reachable failure after one iteration, one no execution reaches within 10 iterations, a failure without a loop (no notes), an unmeasured loop (both notes, the failure reached with no iteration), a division by zero in the loop's body (its copy in the 4th iteration), a failed invariant (no confirmation), two assertions where the one after the loop fails sooner but is another check, a division whose division by zero fails an iteration before its reported overflow (copies are of the failed kind), a path that skips its loop (no notes), and a proof (no notes);--confirm-bound=0; the JSONnotes; and--confirm-boundwith BMC rejected.function_templates.cppkeeps Clang's instantiation note right after its error.Each mutant below, applied alone, makes
suite/inductive_counterexamples.cppfail:--confirm-boundis accepted with BMCI4 survived the first run, which had no check of another kind at the failed one's place;
two_kinds(a division whose division by zero fails an iteration before its reported overflow) kills it.Docs
--confirm-boundand the notes, in text and JSON.Checked
clang/test/Verifyon this branch: 479 passed, 2 failed:function_templates.cpp, whose error was followed by the new notes before Clang's instantiation note (the notes now come after it), anddocs_coverage.test, which asked for the new behavior in the documentation (added). Both pass now.docs_examples.test, with the output shown.