Motivation
If you want a function to appear in specs (preconditions, postconditions), it must be pure. But pure requires a single-expression body -- it can't have loops or multi-statement logic. If the implementation is more complex than a pure expression, your only option is to write a trusted pure stub in a .gobra file: a bodyless declaration whose postconditions you assert without proof. Gobra accepts it unconditionally. If the real function's behavior changes, nothing checks that the stub stays in sync; that invariant is entirely on the author to maintain.
Proposal
I propose to add a new keyword, verified, that gives an impure function unconditional, heap-free post-conditions, that can then be used in specifications without holding any permissions.
How it Works
A verified function or method is:
- Verified by Gobra like any other function. Gobra proves that the pre- and post-conditions hold for the function.
- Encoded as a Viper domain axiom. After verification, the postcondition is lifted into a Viper domain so it holds unconditionally for all arguments, independent of heap state.
- Callable from any specification context. Because the axiom requires no heap, a verified function can appear in
requires, preserves, ensures, ghost assignments, and as arguments to other verified calls.
Each verified function f with postcondition Q produces a standard Viper domain f_domain containing a standard Viper domain function f_spec(args): RetType and one axiom ∀ args :: pre(args) ⟹ f_spec(args) == Q(args) for each ensures and preserves clause.
Semantics
The following things can be verified (after this I'll say "function" for convenience, but always mean all of the following):
- Functions whose spec makes no references to the heap
- Methods whose spec makes no references to the heap, including pointer dereferences
- Interface method signatures -- Gobra automatically verifies all methods actually used as that type somewhere in the code being verified, which must be tagged as
verified and have preconditions not stronger than, and postconditions not weaker than, the interface method declaration
A verified function can't be pure or trusted, because they have conflicting encoding strategies. It must have a body (except interface signatures), so there's something to verify. It must return at least one result, and have at least one ensures clause, so it yields at least one nontrivial axiom. It must have a decreases clause, because non-terminating functions can't yield axioms. Its spec clauses can't contain acc(), old(), *ptr, or recv.field where recv is a pointer, because axioms can't reference the heap. A verified function body can't contain assume or inhale, because that can lead to invalid proofs in Viper.
Call Tree Restrictions
The spec clauses of a verified function can call other functions that are verified or pure. All verified or pure functions called from the specs of f(), directly or via other verified or pure functions, must all have heap-free specs. This prevents the verified function from implicitly involving the heap in its specs. Note that trusted pure functions are relied on as axioms without checking that the implementation actually enforces those axioms, which is exactly what the verified keyword is being proposed to avoid. I would especially discourage it in specs.
Similarly, the call graph rooted at f(), including calls in both the specs and body, restricted to the connected component of f() containing only verified and pure functions, must be acyclic. This is necessary to prevent circular axioms and non-terminating proofs.
Motivation
If you want a function to appear in specs (preconditions, postconditions), it must be pure. But pure requires a single-expression body -- it can't have loops or multi-statement logic. If the implementation is more complex than a pure expression, your only option is to write a trusted pure stub in a
.gobrafile: a bodyless declaration whose postconditions you assert without proof. Gobra accepts it unconditionally. If the real function's behavior changes, nothing checks that the stub stays in sync; that invariant is entirely on the author to maintain.Proposal
I propose to add a new keyword,
verified, that gives an impure function unconditional, heap-free post-conditions, that can then be used in specifications without holding any permissions.How it Works
A
verifiedfunction or method is:requires,preserves,ensures, ghost assignments, and as arguments to other verified calls.Each verified function
fwith postconditionQproduces a standard Viper domainf_domaincontaining a standard Viper domain functionf_spec(args): RetTypeand one axiom∀ args :: pre(args) ⟹ f_spec(args) == Q(args)for eachensuresandpreservesclause.Semantics
The following things can be
verified(after this I'll say "function" for convenience, but always mean all of the following):verifiedand have preconditions not stronger than, and postconditions not weaker than, the interface method declarationA
verifiedfunction can't bepureortrusted, because they have conflicting encoding strategies. It must have a body (except interface signatures), so there's something to verify. It must return at least one result, and have at least oneensuresclause, so it yields at least one nontrivial axiom. It must have a decreases clause, because non-terminating functions can't yield axioms. Its spec clauses can't containacc(),old(),*ptr, orrecv.fieldwhererecvis a pointer, because axioms can't reference the heap. Averifiedfunction body can't containassumeorinhale, because that can lead to invalid proofs in Viper.Call Tree Restrictions
The spec clauses of a
verifiedfunction can call other functions that areverifiedorpure. Allverifiedorpurefunctions called from the specs off(), directly or via otherverifiedorpurefunctions, must all have heap-free specs. This prevents the verified function from implicitly involving the heap in its specs. Note thattrusted purefunctions are relied on as axioms without checking that the implementation actually enforces those axioms, which is exactly what theverifiedkeyword is being proposed to avoid. I would especially discourage it in specs.Similarly, the call graph rooted at
f(), including calls in both the specs and body, restricted to the connected component off()containing onlyverifiedandpurefunctions, must be acyclic. This is necessary to prevent circular axioms and non-terminating proofs.