Skip to content

Add a flag for vacuity check#2307

Open
pennyannn wants to merge 2 commits into
verus-lang:mainfrom
pennyannn:vacuity
Open

Add a flag for vacuity check#2307
pennyannn wants to merge 2 commits into
verus-lang:mainfrom
pennyannn:vacuity

Conversation

@pennyannn
Copy link
Copy Markdown

@pennyannn pennyannn commented Apr 7, 2026

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

Add vacuity checking (-V check-vacuity)

Summary

This PR adds a new flag -V check-vacuity that detects vacuous proofs — proofs that succeed only because the assumptions are contradictory, not because the conclusion actually follows.

Motivation

When a function's requires clauses, trait bounds, type invariants, or call postconditions are contradictory, any ensures clause is trivially provable. This can hide real specification bugs, especially when using external_body or assume_specification with incorrect ensures clauses.

Approach

The implementation follows the Dafny/Boogie approach for detecting contradictory assumptions, adapted for Verus's architecture.

When the flag is enabled, after a successful normal verification, Verus runs a second SMT query using a Boogie-style tracked WP encoding:

  • Each assume H becomes (v ==> H) ==> rest with a tracked boolean v
  • Each assert G becomes (a ∧ G) ∧ rest with a tracked boolean a
  • Each local axiom (requires, trait bounds, type invariants) becomes (v ==> axiom) with a tracked boolean v

Each tracking variable is asserted as (assert (! v :named vacuity$v)) so it appears in the SMT unsat core. After check-sat returns unsat, the unsat core reveals which tracking variables were needed. If any assertion's tracking variable is absent, that assertion was proved without needing the goal — the assumptions are contradictory.

The IMPLIES encoding for assumptions and AND encoding for assertions is essential due to how negation interacts with the WP structure. See source/VACUITY.md for a detailed explanation with examples.

Output

Given this input with one correct and one vacuous function:

proof fn good(x: int)
    requires x > 3,
    ensures x >= 3,
{}

proof fn bad(x: int)
    requires x > 10, x < 5,
    ensures x == 42,
{}

The output is:

error: function body check: vacuity check failed: the hypotheses are inconsistent (they imply false), so the proof is vacuously true
  --> test.rs:7:7
   |
 7 | proof fn bad(x: int)
   |       ^^^^^^^^^^^^^^

verification results:: 2 verified, 0 errors
vacuity check:: 1 passed, 1 errors
  • good: verified successfully, vacuity check passed
  • bad: verified successfully (contradictory requires make any ensures trivially true), but vacuity check caught it — counted as 1 error in both lines

When all functions are correct:

verification results:: 2 verified, 0 errors
vacuity check successful:: 2 passed, 0 errors

Testing

  • 145 AIR unit tests pass (140 existing + 5 new)
  • 21 Verus integration tests pass (all new)
  • All existing tests unaffected (flag defaults to off)

Reference

Aaron Tomb and Anjali Joshi. "Static Coverage in Deductive Software Verification." In Formal Methods in Computer-Aided Design (FMCAD), 2025.

@briangmilnes
Copy link
Copy Markdown

briangmilnes commented Apr 7, 2026 via email

@pennyannn
Copy link
Copy Markdown
Author

pennyannn commented Apr 7, 2026

Things that could be implemented to further improve the user experience:

  1. Currently, report of error is on function level. It would be nice to explore tracking unsat core assumptions and which program assertion could be vacuously proved, maybe through desugering of label as is described in the paper. Or something similar but tailored to Verus. For now, one can insert assert(false) in various program branches to find the assertion and resort to running the SMT query again to reverse engineer the unsat core.
  2. Add an annotation to allow certain assertion to be skipped for vacuity check.

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.

2 participants