Skip to content

Commit 7f3c13a

Browse files
address last bits of feedback
1 parent b9243e4 commit 7f3c13a

1 file changed

Lines changed: 18 additions & 9 deletions

File tree

rfc/src/rfcs/0014-harness-partition.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
## Summary
1111

1212
It can often be useful to subdivide an expensive proof harness so that different parts of the input space are verified separately.
13+
This is also known as *proof by cases* or a *case split*.
1314
Adding the built-in ability to partition a proof harness into different pieces (that each make differing assumptions about their inputs) could reduce the cost of expensive proofs, while allowing Kani to automatically check that the partitions cover the entire input space and, thus, will be equivalent to a single-harness proof.
1415

1516
## User Impact
@@ -34,7 +35,7 @@ pub fn proof_harness() {
3435
```
3536

3637
Since there are two tricky to analyze function calls, but only one will ever be called on a given input, you might want to verify all values of `input` where `input > 0` that will take the first branch separately from those that will take the second.
37-
This way, each solve will only have to reason about two of the three complex function calls, and you could use Kani's parallel proof runner to run both proofs at once.
38+
This way, each verification run will only have to reason about two of the three complex function calls, and you could use Kani's parallel proof runner to run both proofs at once.
3839

3940
The best way to currently do this is by manually partitioning out these paths into two proof harnesses.
4041

@@ -56,7 +57,7 @@ One could also write preconditions for `very_complex_fn_{1_2}` that restrict `in
5657

5758
However, either of these strategies:
5859
- **can affect soundness**--there's no guarantee that your partitions will fully span the space of possible inputs.
59-
The only way to determine that a set of proofs like the one above are incorrect (as it forgets to verify the value of 0 for `input`) is by manual inspection.
60+
The only way to determine that a set of proofs like the one above are incomplete (as it forgets to verify the value of 0 for `input`) is by manual inspection.
6061
This gets infeasible for proofs with complex partition rules like those found in the [proofs for the standard library's unchecked multiplication](https://github.com/model-checking/verify-rust-std/blob/1c4ea17a99b9202f96608473083998b116bb6508/library/core/src/num/mod.rs#L1818-L1836).
6162
- **increases user burden**--instead of having to write and maintain a single proof, the user now has to handle a proof for each partition.
6263

@@ -72,7 +73,7 @@ For example, the above would become
7273
```rust
7374
#[kani::proof]
7475
pub fn partitioned_proof() {
75-
kani::partition::<i32>([|a| *a > 0, |a| *a < 0], |input: i32| target_fn(input));
76+
kani::partition::<i32, _, _>([|a| *a > 0, |a| *a < 0], |input: i32| assert!(target_fn(input) > 0));
7677
}
7778
```
7879

@@ -124,15 +125,15 @@ pub fn partition<T: Arbitrary, R, const N: usize>(
124125
```
125126

126127
This signature makes a few key design decisions:
127-
- *It takes in an array of `conditions` with the specific length `const N`*, ensuring the number of harnesses we have to generate is knowable at compile time when we're doing partition generation.
128+
- *It takes in an array of `conditions` with the specific length `const N`*, ensuring the number of harnesses we have to generate is known at compile time when we're doing partition generation.
128129
- *Partition conditions & the partition closure are represented by function pointers*.
129130
This allows the user to provide closures of the same signature, but only if those closures do not capture any state.
130131
Allowing conditions closures to capture runtime state like local variables would be unwise as partitions are conceptually static divisions of a proof which should not depend on runtime values.
131132
- *The function returns the same type `R` as the underlying partition closure*, with the thought being that value could then be used in later computation without affecting the mechanics of a partition.
132133
- *The partition variable is bound by `T: Arbitrary`*.
133134
This would allow us to construct a new non-deterministic value with `kani::any()` that can then be passed to the partition closure.
134135

135-
When the `kani-compiler` encounters a call to this function, it will transparently replace the entire `#[kani::proof()]` that called it with a set of new generated functions that, as a whole, will represent the partitioned proof. This new partitioned proof would contain:
136+
When the `kani-compiler` encounters a call to this function, it will transparently replace the entire `#[kani::proof]` that called it with a set of new generated functions that, as a whole, will represent the partitioned proof. This new partitioned proof would contain:
136137

137138
1. one new proof harness for each partition
138139

@@ -145,7 +146,7 @@ pub fn partitioned_proof_partition_1() {
145146
/* OTHER CODE BEFORE THE CALL TO `kani::partition` IF THERE WAS ANY */
146147
let partition_variable = kani::any::<i32>();
147148
kani::assume((|a: &i32| *a > 0)(&partition_variable));
148-
(|input: i32| target_fn(input))(partition_variable);
149+
(|input: i32| assert!(target_fn(input) > 0))(partition_variable);
149150
}
150151
```
151152

@@ -180,13 +181,20 @@ pub fn strange_partitioned_proof() {
180181
```
181182

182183
The behavior of a partition in this case seems ill-defined, so Kani will detect cases where the partition conditions are not constant closures or function items and emit a compile error.
184+
This would likely require new analysis from Kani to properly detect these cases.
183185

184186
## Rationale and alternatives
185187

186-
### 1. Allowing overlapping partition conditions
188+
### 1. `kani::partition` as an attribute
189+
We had initially considered implementing the partition as an attribute placed on a proof harness, similar to `kani::proof`.
190+
However, we decided it would be best implemented as a function because a partition conceptually represents a split on variables in the proof, rather than something inherent to the proof itself.
191+
Similarly, we wanted to maintain the convention that proof harnesses do not take in any inputs, and all attribute solutions would have to break that.
192+
193+
### 2. Allowing overlapping partition conditions
187194
As a corollary to checking that partitions span the space of all possible inputs, I had initially considered giving a warning if partitions are overlapping as this could indicate ill-defined bounds.
188195

189-
However, this kind of overlap would not affect the correctness of the partition, as every possible value of the partition variable would still be checked, just potentially more than once. Admittedly, it's fairly difficult to come up with a realistic use case in which an overlap is helpful for users, so whether this should be allowed is up for debate.
196+
However, this kind of overlap would not affect the correctness of the partition, as every possible value of the partition variable would still be checked, just potentially more than once.
197+
Admittedly, it's fairly difficult to come up with a realistic use case in which an overlap is helpful for users, so whether this should be allowed is up for debate.
190198

191199
## Open questions
192200

@@ -199,6 +207,7 @@ Potentially through a separate function `partition_bounded` which would be gener
199207
Users may only want to test a subset of the input space (e.g., only testing multiplication for certain integer ranges), but the current design mandates that they cover the whole input space.
200208
We could potentially introduce a separate API, e.g. `unsafe_partition`, that allows this, or some other mechanism to skip the coverage check.
201209
6. Should overlapping partition conditions be allowed?
202-
7. Can we tell the user which section(s) of the input space they're missing?
210+
7. (Similar to #3) How can a coverage check failure be best presented to users?
211+
Perhaps by running the coverage harness with concrete playback to generate a test case that doesn't fall into the partitions?
203212

204213
[^unstable_feature]: This unique ident should be used to enable features proposed in the RFC using `-Z <ident>` until the feature has been stabilized.

0 commit comments

Comments
 (0)