Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resources, Obligations, Time Reasoning #1408

Open
wants to merge 90 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
09e39b3
Add resource predicates
Pialex99 Nov 9, 2022
412f1d2
Add tests for time resources
Pialex99 Nov 22, 2022
14f7b3c
Fix unfolding issue
Pialex99 Nov 22, 2022
4d249ad
Add resource predicates generation
Pialex99 Nov 23, 2022
73ea7bc
Feedback
Pialex99 Nov 25, 2022
2d10a22
Add flag to enable time reasoning
Pialex99 Nov 29, 2022
7830d6c
Add more test for time reasoning
Pialex99 Nov 29, 2022
9d94c35
Add new tick builtin method
Pialex99 Dec 13, 2022
a4e7fdb
Add calls to tick in loops
Pialex99 Dec 14, 2022
69a868c
Update tests
Pialex99 Dec 16, 2022
c6e8686
Add more tests
Pialex99 Dec 21, 2022
f9f093a
Remove calls to tick to an exhale and an inhale for error reporting
Pialex99 Dec 23, 2022
cec311f
More tests and add an expression simplifier for methods
Pialex99 Dec 23, 2022
72de7d2
Formating
Pialex99 Dec 23, 2022
c44f07c
Add position to time credits/receipts expr
Pialex99 Jan 3, 2023
637c27e
Add temporary error reporting
Pialex99 Jan 4, 2023
6e463fa
Add more complex tests
Pialex99 Jan 4, 2023
b6a2b5f
Fix position for error reporting
Pialex99 Jan 4, 2023
284fb6e
Fix rebase
Pialex99 Jan 6, 2023
98b18c8
Fix clippy error
Pialex99 Jan 6, 2023
4773f98
Pull request reviews
Pialex99 Jan 11, 2023
4994088
Add scope id to resource predicate for loop body
Pialex99 Jan 17, 2023
9f4d2bd
Add functions to modify resource predicate scope
Pialex99 Jan 17, 2023
a9a52b5
Add loop cost
Pialex99 Jan 18, 2023
4fafe78
More quadratic loops in a seperate test
Pialex99 Jan 19, 2023
6fe96ed
Clippy
Pialex99 Jan 25, 2023
51df834
Fix small issues from rebase
vfukala May 14, 2023
7dcdba2
Implement hardcoded PyRefObligation without leak checks yet
vfukala May 14, 2023
722091e
Add forperm expressions and (very hacky) leak checks
vfukala May 15, 2023
f3ab30e
Replace PyRefObligations with generic obligations in VIR
vfukala May 19, 2023
6720901
Add general obligations (quite hacky so far)
vfukala May 22, 2023
cd5b895
Fix obligation access with struct arguments by also patching it in sn…
vfukala Jun 4, 2023
d686cce
Add amount and scope_id to obligations
vfukala Jun 5, 2023
1c9da0e
Add a few demo programs
vfukala Jun 5, 2023
384a9ee
Add alloc-dealloc demo program
vfukala Jun 5, 2023
d0e938f
Add demos, preprocess quantifiers, disable fix_quantifiers optimization
vfukala Jun 12, 2023
7b52866
Add a simple demo with struct as an argument to an obligation
vfukala Jun 18, 2023
1aab38c
Add leak checks to poly. VIR and generate them also in loops
vfukala Jun 19, 2023
0ef8d03
Fix compile errors from last update
vfukala Jun 19, 2023
a3a5802
Revert import reformatting in procedure encoder
vfukala Jun 19, 2023
efefb87
Revert reformatting of the first half of the procedure encoder
vfukala Jun 20, 2023
8d8bcfb
A few things missed in last commit
vfukala Jun 20, 2023
1543569
Revert the rest of reformatting in procedure encoder
vfukala Jun 20, 2023
5c397d7
Revert remaining reformatting
vfukala Jun 20, 2023
3cd5691
Add back an accidentally removed trace! invocation
vfukala Jun 20, 2023
3042a5a
Don't include guard in loop invariant if it inhales an impure expression
vfukala Jun 20, 2023
0e67084
Fix folding optimization for obligations, add pass tests for obligations
vfukala Jun 25, 2023
cb9563b
Replace leak check statements by leak check expressions
vfukala Jun 27, 2023
44c17cd
Remove commented code from obligation parsing
vfukala Jun 27, 2023
45eb0f7
Autoformat
vfukala Jun 27, 2023
088df18
Satisfy Clippy
vfukala Jun 27, 2023
e9be356
Fix method purifier crash due to forperm
vfukala Jun 29, 2023
6bb8e38
Revert back to leak check statements in VIR; correctly report obligat…
vfukala Jul 2, 2023
c132824
Fix a missing check before adding a loop guard to the invariant
vfukala Jul 2, 2023
52d4641
Only split quantifiers if body impure; check for and report misplaced…
vfukala Jul 3, 2023
cab9aa2
Properly attach position info to float backend functions; move #729 t…
vfukala Jul 4, 2023
4007982
Proper error reporting for 'not enough resources' verification errors
vfukala Jul 6, 2023
935fa33
Fix err. mngr. misconfig, improve obl. arg. err. msg, add simple obl.…
vfukala Jul 6, 2023
ea06554
Add some obligation loop fail tests, fix error manager for TR
vfukala Jul 7, 2023
7d2a265
Fix error manager
vfukala Jul 7, 2023
78f7664
Merge remote-tracking branch 'upstream/master' into simplest-obligation
vfukala Jul 7, 2023
70882ce
Fix error manager again?
vfukala Jul 7, 2023
b5e2447
Try disabling verification cache
vfukala Jul 7, 2023
1294196
Revert supposed error managers fixes
vfukala Jul 7, 2023
78b83f5
Add not enough resources for precondition err. msg., add obl. tests
vfukala Jul 7, 2023
c7b13e8
Correctly report quantified-resource-not-injective errors
vfukala Jul 8, 2023
1a54d12
Autoformat
vfukala Jul 8, 2023
1e86dc5
Remove mistakenly added file
vfukala Jul 8, 2023
654049f
Generate Viper assumes instead of inhales for prusti_assume!
vfukala Jul 9, 2023
006413e
Add prusti_exhale and prusti_inhale (no error reporting yet)
vfukala Jul 9, 2023
0463263
Consolidate direct specs, add error reporting and tests for them
vfukala Jul 9, 2023
281e5b7
Autoformat
vfukala Jul 9, 2023
8eeaf4c
Rewrite QP desugaring, tweak SwichInt terminator encoding
vfukala Jul 9, 2023
197eeab
Copy elimination of double Not in spec encoding from #1411
vfukala Jul 9, 2023
130364a
Tweak generation of conditionals for better misplaced impure err. rep.
vfukala Jul 10, 2023
b1ea85f
Temporarily revert double Not elimination; wait for #1411 to fix errors
vfukala Jul 10, 2023
57a2032
Unify with TR
vfukala Jul 13, 2023
4314482
Allow doc attributes in resources/obligations
vfukala Jul 13, 2023
133a75f
More misplaced impr. checks; move QR desugaring to a separate module
vfukala Jul 14, 2023
ce05b40
Report spec-only items in non-spec code
vfukala Jul 14, 2023
64aff6d
Update tests for predicates in non-spec code
vfukala Jul 14, 2023
10d977f
Fix a test with time_credits in non-spec code
vfukala Jul 15, 2023
600a4b0
Add resources, obligations, and time reasoning to user guide
vfukala Jul 15, 2023
e114508
Add missing ignore to snippets in the user guide
vfukala Jul 15, 2023
7b772d1
Temporarily remove snap from spec-only checks
vfukala Jul 18, 2023
5ce5d14
Patch resources in pure function contracts
vfukala Jul 23, 2023
aeeea30
Fix stack overflow for non-trusted functions calling themselves in th…
vfukala Jul 26, 2023
54e0d9b
Merge remote-tracking branch 'upstream/master' into simplest-obligation
vfukala Jul 28, 2023
6a625e6
Bump prusti-contracts versions
vfukala Jul 28, 2023
79d4868
Cleanup
vfukala Jul 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions docs/dev-guide/src/config/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
| [`SMT_SOLVER_WRAPPER_PATH`](#smt_solver_wrapper_path) | `Option<String>` | `None` | A |
| [`SMT_UNIQUE_TRIGGERS_BOUND`](#smt_unique_triggers_bound) | `Option<u64>` | `None` | A |
| [`SMT_UNIQUE_TRIGGERS_BOUND_TOTAL`](#smt_unique_triggers_bound_total) | `Option<u64>` | `None` | A |
| [`TIME_REASONING`](#time_reasoning) | `bool` | `false` | A |
| [`UNSAFE_CORE_PROOF`](#unsafe_core_proof) | `bool` | `false` | A |
| [`USE_MORE_COMPLETE_EXHALE`](#use_more_complete_exhale) | `bool` | `true` | A |
| [`USE_SMT_WRAPPER`](#use_smt_wrapper) | `bool` | `false` | A |
Expand Down Expand Up @@ -322,6 +323,7 @@ Comma-separated list of optimizations to enable, or `"all"` to enable all. Possi
- `"purify_vars"`
- `"fix_quantifiers"`
- `"fix_unfoldings"`
- `"simplify_exprs"`
- `"remove_unused_vars"`
- `"remove_trivial_assertions"`
- `"clean_cfg"`
Expand Down Expand Up @@ -441,6 +443,13 @@ If not `None`, checks that the total number of unique triggers reported by the S

> **Note:** Requires `USE_SMT_WRAPPER` to be `true`.

## `TIME_REASONING`

When enabled, the `time_credits` and `time_receipts` predicates are usable and functions' and loops' bodies will consume time credits and produce time receipts.

> **Note:** This is an experimental feature.
> **Note:** Requires `SIMPLIFY_ENCODING` to be `true` and the `simplify_exprs` optimization to be enabled.

## `UNSAFE_CORE_PROOF`

When enabled, the new core proof is used, suitable for unsafe code
Expand Down
2 changes: 2 additions & 0 deletions docs/user-guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@
- [Type models](verify/type-models.md)
- [Counterexamples](verify/counterexample.md)
- [Specifications in trait `impl` blocks](verify/impl_block_specs.md)
- [Resources and obligations](verify/resources_obligations.md)
- [Time reasoning](verify/time_reasoning.md)
- [Specification Syntax](syntax.md)
167 changes: 167 additions & 0 deletions docs/user-guide/src/verify/resources_obligations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Resources and obligations

One way of describing state that is otherwise not captured by a Prusti program is using resources and obligations. They are typically produced and consumed by [`#[trusted]`](trusted.md) functions that have some external effects (e.g. by calling functions in foreign non-Rust libraries or by interacting with the real world).

Resources and obligations can appear in the pre- and postconditions of functions. When a function is called, Prusti must prove that all resources in its precondition are held by the caller at the call site. Calling a function then has the effect of
- removing the resources in its precondition from and
- adding the resources in its postcondition to

the caller's state at the call site.

## Resources

A resource can be declared, for example, like so:

```rust,noplaypen,ignore
enum Currency {
CAD,
CHF,
DKK,
}

resource! {
fn cash(amount: usize, currency: Currency);
}
```

The general syntax is

```rust,noplaypen,ignore
resource! {
fn resource_name(amount: usize, ...);
}
```

where `resource_name` can be an arbitrary identifier and `...` here stands for arbitrary function parameters. The first parameter always has to be `amount: usize` (literally) and it has special semantics: it expresses how many resource instances a particular invocation of the resource represents. This means that, for example, asserting `cash(2, Currency::CHF)` is equivalent to asserting `cash(1, Currency::CHF) & cash(1, Currency::CHF)`.

Example program:

```rust,noplaypen
{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/resources_apples.rs:code}}
// Prusti: VERIFIES
```

In the above example, Prusti is able to prove that if the trusted functions have the effects they claim they do (e.g. in the case of `buy_apple`, it removes at most CHF 2 from some hypothetical account of ours and it produces at least one apple in a hypothetical warehouse), then the non-trusted `buy_apple_in_canada` also fulfills its contract (it consumes at most CAD 4 and produces at least 1 apple). Note that the program still verifies if the `4` in `cash(4, Currency::CAD)` is increased to any number larger than `4` because in such a case, there are still enough resources for the `exchange()` calls. Compare this to obligations in a section below.

In a more realistic scenario, resources can be used to establish lower and upper bounds for the number of calls of a particular function. In the following example, by making a call of `costly_function` consume one instance of `call_credits`, we have a natural way of proving upper bounds on the number of times the function is called.

```rust,noplaypen,ignore
resource! {
fn call_credits(amount: usize);
}

#[trusted]
#[requires(call_credits(1))]
fn costly_function(arg: i32) {
// ...
}

#[requires(call_credits(2))]
fn costly_at_most_twice(arg: i32) {
costly_function(arg);
if arg > 0 {
costly_function(arg - 1);
}
}

#[requires(call_credits(10))]
fn complicated_procedure() {
// ... costly_function(...) ... costly_at_most_twice(...) ...

// if this program verifiers, we know that this function calls
// `costly_function` at most ten times
}
```

The same idea is used to prove lower and upper bounds on the running time of functions as explained in the chapter about [time reasoning](time_reasoning.md).

Resources can also be asserted in universal quantifiers and [loop invariants](loop.md):

```rust,noplaypen
{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/resources_clean_rooms.rs:code}}
// Prusti: VERIFIES
```

## Usage of resources in expressions

The Rust functions associated with resources may not be called or otherwise referenced outside of specification code (preconditions, postconditions, `prusti_assert!`, etc.). Inside specification code, they may only appear
- alone,
- as conjuncts with other expressions inside the non-short-circuiting And (`&`),
- inside the consequent of an implication,
- inside the body of a universal quantifier,
- inside the then- or else-branch of a conditional.

They are not allowed in other kinds expressions. In particular, not inside
- negations,
- non-short-circuiting disjunctions (`|`),
- the antecedents of implications,
- the bodies of existential quantifiers,
- the guards of conditionals.

The short-circuiting `&&` and `||` are equivalent to conditionals:
- `A && B` is equivalent to `if A { B } else { false }`, and
- `A || B` is equivalent to `if A { true } else { B }`.

Therefore, resources are allowed to appear on the left-hand side of `&&` and `||` but not on the right-hand side.

## Pure functions
Resources may appear in the preconditions of [`#[pure]`](pure.md) functions but not in their postconditions. When a pure function is called, the resources in the precondition are asserted (so verification fails if Prusti cannot prove that the caller has all the required resources), but they are not removed from the caller's state.

> Note: The behavior of resources in the contracts of pure functions as described here might be not fully implemented yet and it might change in the future.

## Obligations

Obligations are declared and used almost identically to resources. The only two differences are

* obligations are declared using the `obligation!` macro instead of `resource!`, but otherwise, the declarations have the same structure, and
* for a program to verify, Prusti must prove that no instances of obligations are leaked.

Obligations are leaked if at the end of a function, not all obligations that are held are asserted in the function postcondition or if from one loop iteration to another, more obligations are acquired than what the change of the obligations asserted in the [loop invariant](loop.md) is.

In the following example, `with_resources` verifies, but `with_obligations` doesn't because obligations are leaked.

```rust,noplaypen
{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/resources_vs_obligations.rs:code}}
// Prusti: FAILS
```

Obligations are primarily used for proving that all allocated resources are eventually deallocated. If we have only one trusted function which allocates some kind of objects and only one function which deallocates them, we can annotate the allocating function with `#[ensures(obligation_name(...))]` and the deallocating one with `#[requires(obligation_name(...))]`. Then if our program verifier, we have the guarantee that for an arbitrary function, all objects allocated by it are also deallocated by it or are asserted in its postcondition. See the example below.

```rust,noplaypen
{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/obligation_leak.rs:code}}
// Prusti: FAILS
```

`procedure1` verifies successfully. For `procedure2`, on the other hand, we get a verification error because if it exits through the `return;`, the allocated object does not get deallocated.

## Loops
When a [loop](loop.md) is encountered, only as many resources are taken from the currently executing function's state as the loop invariant in the first iteration asserts. At the end of the loop, as many resources are given back to the state of the enclosing function as the loop invariant in the last iteration asserts (plus/minus those produced/consumed in the last iteration after the invariant).

Naturally then, it is not considered an obligation leak when the loop invariant in the first loop iteration doesn't assert all obligations currently held in the enclosing function since those that are not asserted just stay in the function's state for after the loop and are in that sense not leaked.

Hence, an obligation leak inside a loop occurs only when the amount of an obligation held in one loop iteration increases more than the amount of this obligation asserted in the loop invariant.

By this logic, the following program verifies.

```rust,noplaygen
{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/loop_resource_partial_assert.rs:code}}
// Prusti: VERIFIES
```

## `prusti_exhale!` and `prusti_inhale!`

`prusti_exhale!` and `prusti_inhale!` are close companions of [`prusti_assert!` and `prusti_assume!`](assert_refute_assume.md). They allow the full [Prusti specification syntax](../syntax.md) and for assertions that don't access any resources (here, we use _resources_ to refer to both _resources_ and _obligations_), `prusti_exhale!(...)` does the same as `prusti_assert!(..)` and `prusti_inhale!(...)` the same as `prusti_assume!(...)`. If the assertion contains resources, `prusti_exhale!` consumes them and `prusti_inhale!` produces them. Here are several examples of functions that verify:

```rust,noplaypen
{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/inhale_exhale.rs:code}}
// Prusti: VERIFIES
```

and here a few that don't:

```rust,noplaypen
{{#rustdoc_include ../../../../prusti-tests/tests/verify/fail/user-guide/inhale_exhale.rs:code}}
// Prusti: FAILS
```

Similarly to `prusti_assume!`, `prusti_exhale!` and `prusti_inhale!` may introduce unsoundness. They can be a helpful tool for trying out verification approaches or debugging verification errors, which is what they are primarily meant to be used for.
50 changes: 50 additions & 0 deletions docs/user-guide/src/verify/time_reasoning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Time reasoning

With the setting [`TIME_REASONING=true`](https://viperproject.github.io/prusti-dev/dev-guide/config/flags.html#time_reasoning), two built-in [resources](resources_obligations.md) are available: `time_credits` and `time_receipts`. They can be used to prove lower and upper bounds on the running time of functions.

For the most part, they behave as any other resource defined by the user using the [`resource!` macro](resources_obligations.md). However, they are special in that at the beginning of every function and in every loop iteration (specifically, right after the [loop invariant](loop.md)), one instance of `time_credits` is consumed and one instance of `time_receipts` is produced. If enough `time_credits` might not be available, verification fails with an error.

Since the number of `time_credits` asserted in a function's precondition is an upper bound on the number of `time_credits` consumed inside the function, it serves as an upper bound on the running time of the function. Similarly, asserting a certain number of `time_receipts` in the postcondition entails a lower bound on the function's running time.

In the following example, we make these two bounds match and therefore show that the function always needs exactly `n + 1` time steps to execute.

```rust,noplaypen
{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/time_loop.rs:code}}
// Prusti: VERIFIES
```

The number of time steps a function needs to execute might also depend on the input in more complicated ways. See the next example.

```rust,noplaygen
{{#rustdoc_include ../../../../prusti-tests/tests/verify/pass/user-guide/time_more_complicated.rs:code}}
// Prusti: VERIFIES
```

The following are three more equivalent ways of specifying the same precondition of `do_work` as above.

```rust,noplaygen,ignore
#[requires(time_credits(6))]
#[requires(if more_work { time_credits(5) } else { true })]
fn do_work(more_work: bool) {
// ...
}
```

```rust,noplaypen,ignore
#[requires(time_credits(6) & (more_work ==> time_credits(5)))]
fn do_work(more_work: bool) {
// ...
}
```

```rust,noplaypen,ignore
#[requires(more_work ==> time_credits(11))]
#[requires(!more_work ==> time_credits(6))]
fn do_work(more_work: bool) {
// ...
}
```

## Pure functions

[NOT FULLY IMPLEMENTED YET, MIGHT CHANGE IN FUTURE] `time_credits` and `time_receipts` may not appear in preconditions and postconditions of [`#[pure]`](pure.md) functions. For the purposes of time reasoning, the execution of pure functions is taken to be instantaneous: they do not produce or consume any `time_credits` or `time_receipts`.
3 changes: 2 additions & 1 deletion prusti-common/src/vir/fixes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

//! Fix the potentially broken encoding.

pub use self::ghost_vars::fix_ghost_vars;
pub use self::{ghost_vars::fix_ghost_vars, quantified_resources::desugar_quantified_resources};

mod ghost_vars;
mod quantified_resources;
Loading
Loading