Skip to content

Commit 5514a11

Browse files
committed
RFC: Quantifiers
Signed-off-by: Felipe R. Monteiro <felisous@amazon.com>
1 parent 0bb1325 commit 5514a11

File tree

2 files changed

+208
-0
lines changed

2 files changed

+208
-0
lines changed

rfc/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
- [0007-global-conditions](rfcs/0007-global-conditions.md)
1616
- [0008-line-coverage](rfcs/0008-line-coverage.md)
1717
- [0009-function-contracts](rfcs/0009-function-contracts.md)
18+
- [0010-quantifiers](rfcs/0010-quantifiers.md)

rfc/src/rfcs/0010-quantifiers.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
- **Feature Name:** Quantifiers
2+
- **Feature Request Issue:** [#2546](https://github.com/model-checking/kani/issues/2546) and [#836](https://github.com/model-checking/kani/issues/836)
3+
- **RFC PR:** [#](https://github.com/model-checking/kani/pull/)
4+
- **Status:** Unstable
5+
- **Version:** 1
6+
7+
-------------------
8+
9+
## Summary
10+
11+
Quantifiers are like logic-level loops and a powerful reasoning helper, which allow us to express statements about objects in a domain that satisfy a given condition. There are two primary quantifiers: the existential quantifier (∃) and the universal quantifier (∀).
12+
13+
1. The existential quantifier (∃): represents the statement "there exists." We use to express that there is at least one object in the domain that satisfies a given condition. For example, "∃x P(x)" means "there exists an x such that P(x) is true."
14+
15+
2. The universal quantifier (∀): represents the statement "for all" or "for every." We use it to express that a given condition is true for every object in the domain. For example, "∀x P(x)" means "for every x, P(x) is true."
16+
17+
## User Impact
18+
19+
Quantifiers enhance users' expressive capabilities, enabling the verification of more intricate properties in a concise manner.
20+
Rather than exhaustively listing all elements in a domain, quantifiers enable users to make statements about the entire domain at once. This compact representation is crucial when dealing with large or unbounded inputs. Quantifiers also facilitate abstraction and generalization of properties. Instead of specifying properties for specific instances, quantified properties can capture general patterns and behaviors that hold across different objects in a domain. Additionally, by replacing loops in the specification with quantifiers, Kani can encode the properties more efficiently within the specified bounds, making the verification process more manageable and computationally feasible.
21+
22+
This new feature doesn't introduce any breaking changes to users. It will only allow them to write properites using the existential (∃) and universal (∀) quantifiers.
23+
24+
## User Experience
25+
26+
We propose a syntax inspired by [Prusti](https://viperproject.github.io/prusti-dev/user-guide/syntax.html?highlight=quanti#quantifiers). Quantifiers must have only one bound variable (restriction imposed today by CBMC). The syntax of existential (i.e., `kani::exists`) and universal (i.e., `kani::forall`) quantifiers are:
27+
28+
```rust
29+
kani::exists(|<bound variable>: <bound variable type>| <range> && <Boolean expression>)
30+
kani::forall(|<bound variable>: <bound variable type>| <range> ==> <Boolean expression>)
31+
```
32+
33+
where `<range>` is an expression of the form
34+
35+
```rust
36+
<lower bound> <= <bound variable> && <bound variable> < <upper bound>
37+
```
38+
39+
where `lower bound` and `upper bound` are constants. The bound predicates could be strict (e.g., `<lower bound> < <bound variable>`), or non-strict (e.g., `<upper bound> <= <bound variable>`), but both the bounds must be **constants**. CBMC's SAT backend only supports bounded quantification under **constant** lower and upper bounds.
40+
41+
Consider the following example adapted from the documentation for the [from_raw_parts](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.from_raw_parts) function:
42+
43+
```rust
44+
use std::ptr;
45+
use std::mem;
46+
47+
#[kani::proof]
48+
fn main() {
49+
let v = vec![kani::any::<usize>(); 100];
50+
51+
// Prevent running `v`'s destructor so we are in complete control
52+
// of the allocation.
53+
let mut v = mem::ManuallyDrop::new(v);
54+
55+
// Pull out the various important pieces of information about `v`
56+
let p = v.as_mut_ptr();
57+
let len = v.len();
58+
let cap = v.capacity();
59+
60+
unsafe {
61+
// Overwrite memory
62+
for i in 0..len {
63+
*p.add(i) += 1;
64+
}
65+
66+
// Put everything back together into a Vec
67+
let rebuilt = Vec::from_raw_parts(p, len, cap);
68+
}
69+
}
70+
```
71+
72+
Given the `v` vector has non-deterministic values, there are potential arithmetic overflows that might happen in the for loop. So we need to constrain all values of the array. We may also want to check all values of `rebuilt` after the operation. Without quantifiers, we might be tempt to use loops as follows:
73+
74+
```rust
75+
use std::ptr;
76+
use std::mem;
77+
78+
#[kani::proof]
79+
fn main() {
80+
let original_v = vec![kani::any::<usize>(); 100];
81+
let v = original_v.clone();
82+
for i in 0..v.len() {
83+
kani::assume(v[i] < 5);
84+
}
85+
86+
// Prevent running `v`'s destructor so we are in complete control
87+
// of the allocation.
88+
let mut v = mem::ManuallyDrop::new(v);
89+
90+
// Pull out the various important pieces of information about `v`
91+
let p = v.as_mut_ptr();
92+
let len = v.len();
93+
let cap = v.capacity();
94+
95+
unsafe {
96+
// Overwrite memory
97+
for i in 0..len {
98+
*p.add(i) += 1;
99+
}
100+
101+
// Put everything back together into a Vec
102+
let rebuilt = Vec::from_raw_parts(p, len, cap);
103+
for i in 0..len {
104+
assert_eq!(rebuilt[i], original_v[i]+1);
105+
}
106+
}
107+
}
108+
```
109+
110+
This, however, might unnecessary increase the complexity of the verication process. We can achieve the same effect using quantifiers as shown below.
111+
112+
```rust
113+
use std::ptr;
114+
use std::mem;
115+
116+
#[kani::proof]
117+
fn main() {
118+
let original_v = vec![kani::any::<usize>(); 3];
119+
let v = original_v.clone();
120+
kani::assume(kani::forall(|i: usize| (i < v.len()) ==> (v[i] < 5)));
121+
122+
// Prevent running `v`'s destructor so we are in complete control
123+
// of the allocation.
124+
let mut v = mem::ManuallyDrop::new(v);
125+
126+
// Pull out the various important pieces of information about `v`
127+
let p = v.as_mut_ptr();
128+
let len = v.len();
129+
let cap = v.capacity();
130+
131+
unsafe {
132+
// Overwrite memory
133+
for i in 0..len {
134+
*p.add(i) += 1;
135+
}
136+
137+
// Put everything back together into a Vec
138+
let rebuilt = Vec::from_raw_parts(p, len, cap);
139+
assert!(kani::forall(|i: usize| (i < len) ==> (rebuilt[i] == original_v[i]+1)));
140+
}
141+
}
142+
```
143+
144+
The same principle applies if we want to use the existential quantifier.
145+
146+
```rust
147+
use std::ptr;
148+
use std::mem;
149+
150+
#[kani::proof]
151+
fn main() {
152+
let original_v = vec![kani::any::<usize>(); 3];
153+
let v = original_v.clone();
154+
kani::assume(kani::forall(|i: usize| (i < v.len()) ==> (v[i] < 5)));
155+
156+
// Prevent running `v`'s destructor so we are in complete control
157+
// of the allocation.
158+
let mut v = mem::ManuallyDrop::new(v);
159+
160+
// Pull out the various important pieces of information about `v`
161+
let p = v.as_mut_ptr();
162+
let len = v.len();
163+
let cap = v.capacity();
164+
165+
unsafe {
166+
// Overwrite memory
167+
for i in 0..len {
168+
*p.add(i) += 1;
169+
if i == 10 {
170+
*p.add(i) = 0;
171+
}
172+
}
173+
174+
// Put everything back together into a Vec
175+
let rebuilt = Vec::from_raw_parts(p, len, cap);
176+
assert!(kani::exists(|i: usize| (i < len) && (rebuilt[i] == 0)));
177+
}
178+
}
179+
```
180+
181+
The usage of quantifiers should be valid in any part of the Rust code analysed by Kani.
182+
183+
## Detailed Design
184+
185+
<!-- For the implementors or the hackers -->
186+
187+
Kani should have the same support that CBMC has for quantifiers with its SAT backend. For more details, see [Quantifiers](https://github.com/diffblue/cbmc/blob/0a69a64e4481473d62496f9975730d24f194884a/doc/cprover-manual/contracts-quantifiers.md).
188+
189+
190+
## Open questions
191+
192+
<!-- For Developers -->
193+
- **Function Contracts RFC** - CBMC has support for both `exists` and `forall`, but the
194+
code generation is difficult. The most ergonomic and easy way to implement
195+
quantifiers on the Rust side is as higher-order functions taking `Fn(T) ->
196+
bool`, where `T` is some arbitrary type that can be quantified over. This
197+
interface is familiar to developers, but the code generation is tricky, as
198+
CBMC level quantifiers only allow certain kinds of expressions. This
199+
necessitates a rewrite of the `Fn` closure to a compliant expression.
200+
201+
202+
## Future possibilities
203+
204+
<!-- For Developers -->
205+
- CBMC has an SMT backend which allow the use of quantifiers with arbitrary Boolean expressions. Kani must include an option for users to experiment with this backend.
206+
207+
---

0 commit comments

Comments
 (0)