Skip to content

Commit 2f5bdf2

Browse files
authored
Implement #[kani::async_proof] attribute (rust-lang#1430)
1 parent 16f10e9 commit 2f5bdf2

File tree

6 files changed

+106
-2
lines changed

6 files changed

+106
-2
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,10 @@ dependencies = [
338338
[[package]]
339339
name = "kani_macros"
340340
version = "0.7.0"
341+
dependencies = [
342+
"quote",
343+
"syn",
344+
]
341345

342346
[[package]]
343347
name = "kani_metadata"

library/kani_macros/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ publish = false
1212
proc-macro = true
1313

1414
[dependencies]
15+
quote = "1.0.20"
16+
syn = { version = "1.0.98", features = ["full"] }

library/kani_macros/src/lib.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010

1111
// proc_macro::quote is nightly-only, so we'll cobble things together instead
1212
use proc_macro::TokenStream;
13+
#[cfg(kani)]
14+
use {
15+
quote::quote,
16+
syn::{parse_macro_input, ItemFn},
17+
};
1318

1419
#[cfg(all(not(kani), not(test)))]
1520
#[proc_macro_attribute]
@@ -39,7 +44,7 @@ pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
3944
pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
4045
let mut result = TokenStream::new();
4146

42-
assert!(attr.to_string().is_empty(), "#[kani::proof] does not take any arguments");
47+
assert!(attr.is_empty(), "#[kani::proof] does not take any arguments");
4348
result.extend("#[kanitool::proof]".parse::<TokenStream>().unwrap());
4449
// no_mangle is a temporary hack to make the function "public" so it gets codegen'd
4550
result.extend("#[no_mangle]".parse::<TokenStream>().unwrap());
@@ -51,6 +56,58 @@ pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
5156
// )
5257
}
5358

59+
#[cfg(not(kani))]
60+
#[proc_macro_attribute]
61+
/// Treats #[kani::async_proof] like a normal #[kani::proof] if Kani is not active
62+
pub fn async_proof(attr: TokenStream, item: TokenStream) -> TokenStream {
63+
proof(attr, item)
64+
}
65+
66+
#[cfg(kani)]
67+
#[proc_macro_attribute]
68+
/// Translates #[kani::async_proof] to a #[kani::proof] harness that calls `kani::block_on`, if Kani is active
69+
///
70+
/// Specifically, it translates
71+
/// ```ignore
72+
/// #[kani::async_proof]
73+
/// #[attribute]
74+
/// pub async fn harness() { ... }
75+
/// ```
76+
/// to
77+
/// ```ignore
78+
/// #[kani::proof]
79+
/// #[attribute]
80+
/// pub fn harness() {
81+
/// async fn harness() { ... }
82+
/// kani::block_on(harness())
83+
/// }
84+
/// ```
85+
pub fn async_proof(attr: TokenStream, item: TokenStream) -> TokenStream {
86+
assert!(attr.is_empty(), "#[kani::async_proof] does not take any arguments for now");
87+
let fn_item = parse_macro_input!(item as ItemFn);
88+
let attrs = fn_item.attrs;
89+
let vis = fn_item.vis;
90+
let sig = fn_item.sig;
91+
assert!(sig.asyncness.is_some(), "#[kani::async_proof] can only be applied to async functions");
92+
assert!(
93+
sig.inputs.is_empty(),
94+
"#[kani::async_proof] can only be applied to functions without inputs"
95+
);
96+
let mut modified_sig = sig.clone();
97+
modified_sig.asyncness = None;
98+
let body = fn_item.block;
99+
let fn_name = &sig.ident;
100+
quote!(
101+
#[kani::proof]
102+
#(#attrs)*
103+
#vis #modified_sig {
104+
#sig #body
105+
kani::block_on(#fn_name())
106+
}
107+
)
108+
.into()
109+
}
110+
54111
#[cfg(not(kani))]
55112
#[proc_macro_attribute]
56113
pub fn unwind(_attr: TokenStream, item: TokenStream) -> TokenStream {

tests/expected/async_proof/expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: custom attribute panicked
2+
#[kani::async_proof] does not take any arguments for now
3+
4+
error: custom attribute panicked
5+
#[kani::async_proof] can only be applied to async functions
6+
7+
error: custom attribute panicked
8+
#[kani::async_proof] can only be applied to functions without inputs

tests/expected/async_proof/main.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// compile-flags: --edition 2018
5+
6+
// Tests that the #[kani::async_proof] attribute works correctly
7+
8+
fn main() {}
9+
10+
#[kani::async_proof(foo)]
11+
async fn test_async_proof_with_arguments() {}
12+
13+
#[kani::async_proof]
14+
fn test_async_proof_on_sync_function() {}
15+
16+
#[kani::async_proof]
17+
async fn test_async_proof_on_function_with_inputs(_: ()) {}

tests/kani/AsyncAwait/main.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
// compile-flags: --edition 2018
55

6-
// Tests that the language constructs `async { .. .}` blocks, `async fn`, and `.await` work correctly.
6+
// Tests that the language constructs `async { ... }` blocks, `async fn`, and `.await` work correctly.
77

88
use std::{
99
future::Future,
@@ -13,6 +13,22 @@ use std::{
1313

1414
fn main() {}
1515

16+
#[kani::async_proof]
17+
#[kani::unwind(2)]
18+
async fn test_async_proof_harness() {
19+
let async_block_result = async { 42 }.await;
20+
let async_fn_result = async_fn().await;
21+
assert_eq!(async_block_result, async_fn_result);
22+
}
23+
24+
#[kani::async_proof]
25+
#[kani::unwind(2)]
26+
pub async fn test_async_proof_harness_pub() {
27+
let async_block_result = async { 42 }.await;
28+
let async_fn_result = async_fn().await;
29+
assert_eq!(async_block_result, async_fn_result);
30+
}
31+
1632
#[kani::proof]
1733
#[kani::unwind(2)]
1834
fn test_async_await() {

0 commit comments

Comments
 (0)