Skip to content

Commit a2a1e85

Browse files
fzaisercelinval
andauthored
Allow specifying the scheduling strategy in #[kani_proof] for async functions (rust-lang#1661)
Instead of having to manually wrap the code in `kani::spawnable_block_on` as in rust-lang#1659, this PR allows `#[kani::proof]` to be applied to harnesses that use `spawn` as well. For this to happen, the user has to specify a scheduling strategy. Co-authored-by: Celina G. Val <[email protected]>
1 parent 13bfdd8 commit a2a1e85

File tree

9 files changed

+110
-23
lines changed

9 files changed

+110
-23
lines changed

library/kani/src/futures.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,12 @@ impl Future for JoinHandle {
182182
#[crate::unstable(feature = "async-lib", issue = 2559, reason = "experimental async support")]
183183
pub fn spawn<F: Future<Output = ()> + Sync + 'static>(fut: F) -> JoinHandle {
184184
unsafe {
185-
GLOBAL_EXECUTOR
186-
.as_mut()
187-
.expect("`spawn` should only be called within `block_on_with_spawn`")
188-
.spawn(fut)
185+
if let Some(executor) = GLOBAL_EXECUTOR.as_mut() {
186+
executor.spawn(fut)
187+
} else {
188+
// An explicit panic instead of `.expect(...)` has better location information in Kani's output
189+
panic!("`spawn` should only be called within `block_on_with_spawn`")
190+
}
189191
}
190192
}
191193

library/kani_macros/src/lib.rs

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ use regular as attr_impl;
2222

2323
/// Marks a Kani proof harness
2424
///
25-
/// For async harnesses, this will call [`kani::block_on`] (see its documentation for more information).
25+
/// For async harnesses, this will call [`block_on`] to drive the future to completion (see its documentation for more information).
26+
///
27+
/// If you want to spawn tasks in an async harness, you have to pass a schedule to the `#[kani::proof]` attribute,
28+
/// e.g. `#[kani::proof(schedule = kani::RoundRobin::default())]`.
29+
/// This will wrap the async function in a call to [`block_on_with_spawn`] (see its documentation for more information).
30+
#[proc_macro_error]
2631
#[proc_macro_attribute]
2732
pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
2833
attr_impl::proof(attr, item)
@@ -93,10 +98,13 @@ pub fn derive_arbitrary(item: TokenStream) -> TokenStream {
9398
/// This code should only be activated when pre-building Kani's sysroot.
9499
#[cfg(kani_sysroot)]
95100
mod sysroot {
101+
use proc_macro_error::{abort, abort_call_site};
102+
96103
use super::*;
97104

98105
use {
99106
quote::{format_ident, quote},
107+
syn::parse::{Parse, ParseStream},
100108
syn::{parse_macro_input, ItemFn},
101109
};
102110

@@ -126,7 +134,31 @@ mod sysroot {
126134
};
127135
}
128136

137+
struct ProofOptions {
138+
schedule: Option<syn::Expr>,
139+
}
140+
141+
impl Parse for ProofOptions {
142+
fn parse(input: ParseStream) -> syn::Result<Self> {
143+
if input.is_empty() {
144+
Ok(ProofOptions { schedule: None })
145+
} else {
146+
let ident = input.parse::<syn::Ident>()?;
147+
if ident != "schedule" {
148+
abort_call_site!("`{}` is not a valid option for `#[kani::proof]`.", ident;
149+
help = "did you mean `schedule`?";
150+
note = "for now, `schedule` is the only option for `#[kani::proof]`.";
151+
);
152+
}
153+
let _ = input.parse::<syn::Token![=]>()?;
154+
let schedule = Some(input.parse::<syn::Expr>()?);
155+
Ok(ProofOptions { schedule })
156+
}
157+
}
158+
}
159+
129160
pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
161+
let proof_options = parse_macro_input!(attr as ProofOptions);
130162
let fn_item = parse_macro_input!(item as ItemFn);
131163
let attrs = fn_item.attrs;
132164
let vis = fn_item.vis;
@@ -138,9 +170,13 @@ mod sysroot {
138170
#[kanitool::proof]
139171
);
140172

141-
assert!(attr.is_empty(), "#[kani::proof] does not take any arguments currently");
142-
143173
if sig.asyncness.is_none() {
174+
if proof_options.schedule.is_some() {
175+
abort_call_site!(
176+
"`#[kani::proof(schedule = ...)]` can only be used with `async` functions.";
177+
help = "did you mean to make this function `async`?";
178+
);
179+
}
144180
// Adds `#[kanitool::proof]` and other attributes
145181
quote!(
146182
#kani_attributes
@@ -152,32 +188,44 @@ mod sysroot {
152188
// For async functions, it translates to a synchronous function that calls `kani::block_on`.
153189
// Specifically, it translates
154190
// ```ignore
155-
// #[kani::async_proof]
191+
// #[kani::proof]
156192
// #[attribute]
157193
// pub async fn harness() { ... }
158194
// ```
159195
// to
160196
// ```ignore
161-
// #[kani::proof]
197+
// #[kanitool::proof]
162198
// #[attribute]
163199
// pub fn harness() {
164200
// async fn harness() { ... }
165201
// kani::block_on(harness())
202+
// // OR
203+
// kani::spawnable_block_on(harness(), schedule)
204+
// // where `schedule` was provided as an argument to `#[kani::proof]`.
166205
// }
167206
// ```
168-
assert!(
169-
sig.inputs.is_empty(),
170-
"#[kani::proof] cannot be applied to async functions that take inputs for now"
171-
);
207+
if !sig.inputs.is_empty() {
208+
abort!(
209+
sig.inputs,
210+
"`#[kani::proof]` cannot be applied to async functions that take arguments for now";
211+
help = "try removing the arguments";
212+
);
213+
}
172214
let mut modified_sig = sig.clone();
173215
modified_sig.asyncness = None;
174216
let fn_name = &sig.ident;
217+
let schedule = proof_options.schedule;
218+
let block_on_call = if let Some(schedule) = schedule {
219+
quote!(kani::block_on_with_spawn(#fn_name(), #schedule))
220+
} else {
221+
quote!(kani::block_on(#fn_name()))
222+
};
175223
quote!(
176224
#kani_attributes
177225
#(#attrs)*
178226
#vis #modified_sig {
179227
#sig #body
180-
kani::block_on(#fn_name())
228+
#block_on_call
181229
}
182230
)
183231
.into()

tests/expected/async_proof/expected

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
error: custom attribute panicked
2-
#[kani::proof] does not take any arguments currently
1+
`foo` is not a valid option for `#[kani::proof]`.
32

4-
error: custom attribute panicked
5-
#[kani::proof] cannot be applied to async functions that take inputs for now
3+
`#[kani::proof]` cannot be applied to async functions that take arguments for now

tests/expected/async_proof/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
fn main() {}
99

1010
#[kani::proof(foo)]
11-
async fn test_async_proof_with_arguments() {}
11+
async fn test_async_proof_with_options() {}
1212

1313
#[kani::proof]
14-
async fn test_async_proof_on_function_with_inputs(_: ()) {}
14+
async fn test_async_proof_on_function_with_arguments(_: ()) {}

tests/kani/AsyncAwait/spawn.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,24 @@ use std::sync::{
1111
Arc,
1212
};
1313

14+
#[kani::proof(schedule = kani::RoundRobin::default())]
15+
#[kani::unwind(4)]
16+
async fn round_robin_schedule() {
17+
let x = Arc::new(AtomicI64::new(0)); // Surprisingly, Arc verified faster than Rc
18+
let x2 = x.clone();
19+
let x3 = x2.clone();
20+
let handle = kani::spawn(async move {
21+
x3.fetch_add(1, Ordering::Relaxed);
22+
});
23+
kani::yield_now().await;
24+
x2.fetch_add(1, Ordering::Relaxed);
25+
handle.await;
26+
assert_eq!(x.load(Ordering::Relaxed), 2);
27+
}
28+
1429
#[kani::proof]
1530
#[kani::unwind(4)]
16-
fn round_robin_schedule() {
31+
fn round_robin_schedule_manual() {
1732
let x = Arc::new(AtomicI64::new(0)); // Surprisingly, Arc verified faster than Rc
1833
let x2 = x.clone();
1934
kani::block_on_with_spawn(

tests/ui/arguments-proof/expected

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
= help: message: #[kani::proof] does not take any arguments
1+
`some` is not a valid option for `#[kani::proof]`.
2+
3+
the trait bound `NotASchedule: kani::futures::SchedulingStrategy` is not satisfied

tests/ui/arguments-proof/main.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4-
// kani-flags: --no-unwinding-checks
4+
// kani-flags: --no-unwinding-checks -Z async-lib
55

66
// This test is to check Kani's error handling for harnesses that have proof attributes
77
// with arguments when the expected declaration takes no arguments.
@@ -19,3 +19,12 @@ fn harness() {
1919
assert!(counter < 10);
2020
}
2121
}
22+
23+
// Test what happens if the schedule option is incorrect:
24+
25+
struct NotASchedule;
26+
27+
#[kani::proof(schedule = NotASchedule)]
28+
async fn test() {
29+
assert!(true);
30+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
error: Use of unstable feature `async-lib`: experimental async support
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// compile-flags: --edition 2018
5+
// kani-flags: --no-unwinding-checks
6+
7+
// This test is to check that the `schedule` argument requires an unstable flag.
8+
9+
#[kani::proof(schedule = kani::RoundRobin::default())]
10+
async fn test() {
11+
assert!(true);
12+
}

0 commit comments

Comments
 (0)