Skip to content

Commit 868b13b

Browse files
authored
Re-organize kani_macros lib to improve maintainability (rust-lang#2361)
- Split `proc_macro` implementation into two different modules: - sysroot: module that ships with Kani. - regular: module that is compiled when Kani is imported as a regular crate. - Provide macros to reduce code duplication.
1 parent 3336996 commit 868b13b

File tree

4 files changed

+160
-145
lines changed

4 files changed

+160
-145
lines changed

library/kani/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ publish = false
1212
kani_macros = { path = "../kani_macros" }
1313

1414
[features]
15-
concrete_playback = ["kani_macros/concrete_playback"]
15+
concrete_playback = []

library/kani_macros/Cargo.toml

-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,3 @@ proc-macro2 = "1.0"
1616
proc-macro-error = "1.0.4"
1717
quote = "1.0.20"
1818
syn = { version = "1.0.98", features = ["full"] }
19-
20-
[features]
21-
concrete_playback = []

library/kani_macros/src/lib.rs

+154-139
Original file line numberDiff line numberDiff line change
@@ -13,138 +13,44 @@ mod derive;
1313
// proc_macro::quote is nightly-only, so we'll cobble things together instead
1414
use proc_macro::TokenStream;
1515
use proc_macro_error::proc_macro_error;
16-
#[cfg(kani)]
17-
use {
18-
quote::quote,
19-
syn::{parse_macro_input, ItemFn},
20-
};
2116

22-
#[cfg(any(not(kani), feature = "concrete_playback"))]
23-
#[proc_macro_attribute]
24-
pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
25-
// Leave the code intact, so it can be easily be edited in an IDE,
26-
// but outside Kani, this code is likely never called.
27-
let mut result = TokenStream::new();
28-
29-
result.extend("#[allow(dead_code)]".parse::<TokenStream>().unwrap());
30-
result.extend(item);
31-
result
32-
// quote!(
33-
// #[allow(dead_code)]
34-
// $item
35-
// )
36-
}
17+
#[cfg(kani_sysroot)]
18+
use sysroot as attr_impl;
19+
20+
#[cfg(not(kani_sysroot))]
21+
use regular as attr_impl;
3722

3823
/// Marks a Kani proof harness
3924
///
4025
/// For async harnesses, this will call [`kani::block_on`] (see its documentation for more information).
41-
#[cfg(all(kani, not(feature = "concrete_playback")))]
4226
#[proc_macro_attribute]
4327
pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
44-
let fn_item = parse_macro_input!(item as ItemFn);
45-
let attrs = fn_item.attrs;
46-
let vis = fn_item.vis;
47-
let sig = fn_item.sig;
48-
let body = fn_item.block;
49-
50-
let kani_attributes = quote!(
51-
#[allow(dead_code)]
52-
#[kanitool::proof]
53-
);
54-
55-
assert!(attr.is_empty(), "#[kani::proof] does not take any arguments currently");
56-
57-
if sig.asyncness.is_none() {
58-
// Adds `#[kanitool::proof]` and other attributes
59-
quote!(
60-
#kani_attributes
61-
#(#attrs)*
62-
#vis #sig #body
63-
)
64-
.into()
65-
} else {
66-
// For async functions, it translates to a synchronous function that calls `kani::block_on`.
67-
// Specifically, it translates
68-
// ```ignore
69-
// #[kani::async_proof]
70-
// #[attribute]
71-
// pub async fn harness() { ... }
72-
// ```
73-
// to
74-
// ```ignore
75-
// #[kani::proof]
76-
// #[attribute]
77-
// pub fn harness() {
78-
// async fn harness() { ... }
79-
// kani::block_on(harness())
80-
// }
81-
// ```
82-
assert!(
83-
sig.inputs.is_empty(),
84-
"#[kani::proof] cannot be applied to async functions that take inputs for now"
85-
);
86-
let mut modified_sig = sig.clone();
87-
modified_sig.asyncness = None;
88-
let fn_name = &sig.ident;
89-
quote!(
90-
#kani_attributes
91-
#(#attrs)*
92-
#vis #modified_sig {
93-
#sig #body
94-
kani::block_on(#fn_name())
95-
}
96-
)
97-
.into()
98-
}
28+
attr_impl::proof(attr, item)
9929
}
10030

101-
#[cfg(any(not(kani), feature = "concrete_playback"))]
102-
#[proc_macro_attribute]
103-
pub fn should_panic(_attr: TokenStream, item: TokenStream) -> TokenStream {
104-
// No-op in non-kani mode
105-
item
106-
}
107-
108-
#[cfg(all(kani, not(feature = "concrete_playback")))]
31+
/// Specifies that a proof harness is expected to panic.**
32+
///
33+
/// This attribute allows users to exercise *negative verification*.
34+
/// It's analogous to how
35+
/// [`#[should_panic]`](https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html#testing-panics)
36+
/// allows users to exercise [negative testing](https://en.wikipedia.org/wiki/Negative_testing)
37+
/// for Rust unit tests.
38+
///
39+
/// # Limitations
40+
///
41+
/// The `#[kani::should_panic]` attribute verifies that there are one or more failed checks related to panics.
42+
/// At the moment, it's not possible to pin it down to specific panics.
10943
#[proc_macro_attribute]
11044
pub fn should_panic(attr: TokenStream, item: TokenStream) -> TokenStream {
111-
assert!(attr.is_empty(), "`#[kani::should_panic]` does not take any arguments currently");
112-
let mut result = TokenStream::new();
113-
let insert_string = "#[kanitool::should_panic]";
114-
result.extend(insert_string.parse::<TokenStream>().unwrap());
115-
116-
result.extend(item);
117-
result
118-
}
119-
120-
#[cfg(any(not(kani), feature = "concrete_playback"))]
121-
#[proc_macro_attribute]
122-
pub fn unwind(_attr: TokenStream, item: TokenStream) -> TokenStream {
123-
// When the config is not kani, we should leave the function alone
124-
item
45+
attr_impl::should_panic(attr, item)
12546
}
12647

12748
/// Set Loop unwind limit for proof harnesses
12849
/// The attribute '#[kani::unwind(arg)]' can only be called alongside '#[kani::proof]'.
12950
/// arg - Takes in a integer value (u32) that represents the unwind value for the harness.
130-
#[cfg(all(kani, not(feature = "concrete_playback")))]
13151
#[proc_macro_attribute]
13252
pub fn unwind(attr: TokenStream, item: TokenStream) -> TokenStream {
133-
let mut result = TokenStream::new();
134-
135-
// Translate #[kani::unwind(arg)] to #[kanitool::unwind(arg)]
136-
let insert_string = "#[kanitool::unwind(".to_owned() + &attr.to_string() + ")]";
137-
result.extend(insert_string.parse::<TokenStream>().unwrap());
138-
139-
result.extend(item);
140-
result
141-
}
142-
143-
#[cfg(any(not(kani), feature = "concrete_playback"))]
144-
#[proc_macro_attribute]
145-
pub fn stub(_attr: TokenStream, item: TokenStream) -> TokenStream {
146-
// When the config is not kani, we should leave the function alone
147-
item
53+
attr_impl::unwind(attr, item)
14854
}
14955

15056
/// Specify a function/method stub pair to use for proof harness
@@ -154,45 +60,154 @@ pub fn stub(_attr: TokenStream, item: TokenStream) -> TokenStream {
15460
/// # Arguments
15561
/// * `original` - The function or method to replace, specified as a path.
15662
/// * `replacement` - The function or method to use as a replacement, specified as a path.
157-
#[cfg(all(kani, not(feature = "concrete_playback")))]
15863
#[proc_macro_attribute]
15964
pub fn stub(attr: TokenStream, item: TokenStream) -> TokenStream {
160-
let mut result = TokenStream::new();
161-
162-
// Translate #[kani::stub(original, replacement)] to #[kanitool::stub(original, replacement)]
163-
let insert_string = "#[kanitool::stub(".to_owned() + &attr.to_string() + ")]";
164-
result.extend(insert_string.parse::<TokenStream>().unwrap());
165-
166-
result.extend(item);
167-
result
168-
}
169-
170-
#[cfg(any(not(kani), feature = "concrete_playback"))]
171-
#[proc_macro_attribute]
172-
pub fn solver(_attr: TokenStream, item: TokenStream) -> TokenStream {
173-
// No-op in non-kani mode
174-
item
65+
attr_impl::stub(attr, item)
17566
}
17667

17768
/// Select the SAT solver to use with CBMC for this harness
17869
/// The attribute `#[kani::solver(arg)]` can only be used alongside `#[kani::proof]``
17970
///
18071
/// arg - name of solver, e.g. kissat
181-
#[cfg(all(kani, not(feature = "concrete_playback")))]
18272
#[proc_macro_attribute]
18373
pub fn solver(attr: TokenStream, item: TokenStream) -> TokenStream {
184-
let mut result = TokenStream::new();
185-
// Translate `#[kani::solver(arg)]` to `#[kanitool::solver(arg)]`
186-
let insert_string = "#[kanitool::solver(".to_owned() + &attr.to_string() + ")]";
187-
result.extend(insert_string.parse::<TokenStream>().unwrap());
188-
189-
result.extend(item);
190-
result
74+
attr_impl::solver(attr, item)
19175
}
192-
19376
/// Allow users to auto generate Arbitrary implementations by using `#[derive(Arbitrary)]` macro.
19477
#[proc_macro_error]
19578
#[proc_macro_derive(Arbitrary)]
19679
pub fn derive_arbitrary(item: TokenStream) -> TokenStream {
19780
derive::expand_derive_arbitrary(item)
19881
}
82+
83+
/// This module implements Kani attributes in a way that only Kani's compiler can understand.
84+
/// This code should only be activated when pre-building Kani's sysroot.
85+
#[cfg(kani_sysroot)]
86+
mod sysroot {
87+
use super::*;
88+
89+
use {
90+
quote::{format_ident, quote},
91+
syn::{parse_macro_input, ItemFn},
92+
};
93+
94+
/// Annotate the harness with a #[kanitool::<name>] with optional arguments.
95+
macro_rules! kani_attribute {
96+
($name:ident) => {
97+
pub fn $name(attr: TokenStream, item: TokenStream) -> TokenStream {
98+
let args = proc_macro2::TokenStream::from(attr);
99+
let fn_item = parse_macro_input!(item as ItemFn);
100+
let attribute = format_ident!("{}", stringify!($name));
101+
quote!(
102+
#[kanitool::#attribute(#args)]
103+
#fn_item
104+
).into()
105+
}
106+
};
107+
($name:ident, no_args) => {
108+
pub fn $name(attr: TokenStream, item: TokenStream) -> TokenStream {
109+
assert!(attr.is_empty(), "`#[kani::{}]` does not take any arguments currently", stringify!($name));
110+
let fn_item = parse_macro_input!(item as ItemFn);
111+
let attribute = format_ident!("{}", stringify!($name));
112+
quote!(
113+
#[kanitool::#attribute]
114+
#fn_item
115+
).into()
116+
}
117+
};
118+
}
119+
120+
pub fn proof(attr: TokenStream, item: TokenStream) -> TokenStream {
121+
let fn_item = parse_macro_input!(item as ItemFn);
122+
let attrs = fn_item.attrs;
123+
let vis = fn_item.vis;
124+
let sig = fn_item.sig;
125+
let body = fn_item.block;
126+
127+
let kani_attributes = quote!(
128+
#[allow(dead_code)]
129+
#[kanitool::proof]
130+
);
131+
132+
assert!(attr.is_empty(), "#[kani::proof] does not take any arguments currently");
133+
134+
if sig.asyncness.is_none() {
135+
// Adds `#[kanitool::proof]` and other attributes
136+
quote!(
137+
#kani_attributes
138+
#(#attrs)*
139+
#vis #sig #body
140+
)
141+
.into()
142+
} else {
143+
// For async functions, it translates to a synchronous function that calls `kani::block_on`.
144+
// Specifically, it translates
145+
// ```ignore
146+
// #[kani::async_proof]
147+
// #[attribute]
148+
// pub async fn harness() { ... }
149+
// ```
150+
// to
151+
// ```ignore
152+
// #[kani::proof]
153+
// #[attribute]
154+
// pub fn harness() {
155+
// async fn harness() { ... }
156+
// kani::block_on(harness())
157+
// }
158+
// ```
159+
assert!(
160+
sig.inputs.is_empty(),
161+
"#[kani::proof] cannot be applied to async functions that take inputs for now"
162+
);
163+
let mut modified_sig = sig.clone();
164+
modified_sig.asyncness = None;
165+
let fn_name = &sig.ident;
166+
quote!(
167+
#kani_attributes
168+
#(#attrs)*
169+
#vis #modified_sig {
170+
#sig #body
171+
kani::block_on(#fn_name())
172+
}
173+
)
174+
.into()
175+
}
176+
}
177+
178+
kani_attribute!(should_panic, no_args);
179+
kani_attribute!(unwind);
180+
kani_attribute!(stub);
181+
kani_attribute!(solver);
182+
}
183+
184+
/// This module provides dummy implementations of Kani attributes which cannot be interpreted by
185+
/// other tools such as MIRI and the regular rust compiler.
186+
///
187+
/// This allow users to use code marked with Kani attributes, for example, during concrete playback.
188+
#[cfg(not(kani_sysroot))]
189+
mod regular {
190+
use super::*;
191+
192+
/// Encode a noop proc macro which ignores the given attribute.
193+
macro_rules! no_op {
194+
($name:ident) => {
195+
pub fn $name(_attr: TokenStream, item: TokenStream) -> TokenStream {
196+
item
197+
}
198+
};
199+
}
200+
201+
/// Add #[allow(dead_code)] to a proof harness to avoid dead code warnings.
202+
pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
203+
let mut result = TokenStream::new();
204+
result.extend("#[allow(dead_code)]".parse::<TokenStream>().unwrap());
205+
result.extend(item);
206+
result
207+
}
208+
209+
no_op!(should_panic);
210+
no_op!(unwind);
211+
no_op!(stub);
212+
no_op!(solver);
213+
}

tools/build-kani/src/sysroot.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,17 @@ pub fn build_lib() {
8888
"--config",
8989
"profile.dev.debug-assertions=false",
9090
"--config",
91-
"host.rustflags=[\"--cfg=kani\"]",
91+
"host.rustflags=[\"--cfg=kani\", \"--cfg=kani_sysroot\"]",
9292
"--target",
9393
target,
9494
"--message-format",
9595
"json-diagnostic-rendered-ansi",
9696
];
9797
let mut cmd = Command::new("cargo")
98-
.env("CARGO_ENCODED_RUSTFLAGS", ["--cfg=kani", "-Z", "always-encode-mir"].join("\x1f"))
98+
.env(
99+
"CARGO_ENCODED_RUSTFLAGS",
100+
["--cfg=kani", "--cfg=kani_sysroot", "-Z", "always-encode-mir"].join("\x1f"),
101+
)
99102
.args(args)
100103
.stdout(Stdio::piped())
101104
.spawn()

0 commit comments

Comments
 (0)