Skip to content

Commit 245db28

Browse files
Implement derive macro for Arbitrary (rust-lang#2016)
Introduce a derive macro that allows users to annotate their structs and enums with `#[derive(kani::Arbitray)]`. This will allow users to easily generate implementation of the Arbitrary types for their custom structs and enums when their arbitrary value is a combination of its fields arbitrary values. Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com>
1 parent a301de1 commit 245db28

File tree

23 files changed

+425
-1
lines changed

23 files changed

+425
-1
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,8 @@ dependencies = [
542542
name = "kani_macros"
543543
version = "0.17.0"
544544
dependencies = [
545+
"proc-macro-error",
546+
"proc-macro2",
545547
"quote",
546548
"syn",
547549
]

library/kani_macros/Cargo.toml

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

1414
[dependencies]
15+
proc-macro2 = "1.0"
16+
proc-macro-error = "1.0.4"
1517
quote = "1.0.20"
1618
syn = { version = "1.0.98", features = ["full"] }

library/kani_macros/src/derive.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! This module provides Kani's `derive` macro for `Arbitrary`.
4+
//!
5+
//! ```
6+
//! use kani::Arbitrary;
7+
//!
8+
//! #[derive(Arbitrary)]
9+
//! struct S;
10+
//!
11+
//! ```
12+
use proc_macro2::{Ident, Span, TokenStream};
13+
use proc_macro_error::abort;
14+
use quote::{quote, quote_spanned};
15+
use syn::spanned::Spanned;
16+
use syn::{
17+
parse_macro_input, parse_quote, Data, DataEnum, DeriveInput, Fields, GenericParam, Generics,
18+
Index,
19+
};
20+
21+
pub fn expand_derive_arbitrary(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
22+
let derive_item = parse_macro_input!(item as DeriveInput);
23+
let item_name = &derive_item.ident;
24+
25+
// Add a bound `T: Arbitrary` to every type parameter T.
26+
let generics = add_trait_bound(derive_item.generics);
27+
// Generate an expression to sum up the heap size of each field.
28+
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
29+
30+
let body = fn_any_body(&item_name, &derive_item.data);
31+
let expanded = quote! {
32+
// The generated implementation.
33+
impl #impl_generics kani::Arbitrary for #item_name #ty_generics #where_clause {
34+
fn any() -> Self {
35+
#body
36+
}
37+
}
38+
};
39+
proc_macro::TokenStream::from(expanded)
40+
}
41+
42+
/// Add a bound `T: Arbitrary` to every type parameter T.
43+
fn add_trait_bound(mut generics: Generics) -> Generics {
44+
generics.params.iter_mut().for_each(|param| {
45+
if let GenericParam::Type(type_param) = param {
46+
type_param.bounds.push(parse_quote!(kani::Arbitrary));
47+
}
48+
});
49+
generics
50+
}
51+
52+
/// Generate the body of the function `any()`.
53+
/// This will create the non-deterministic object.
54+
/// E.g.:
55+
/// ```
56+
/// #[derive(Arbitrary)]
57+
/// struct Point { x: u8, y: u8 }
58+
/// ```
59+
/// will generate the following body for `fn any()`:
60+
/// ```
61+
/// fn any() -> Self {
62+
/// Self { x: kani::any(), y: kani::any() }
63+
/// }
64+
/// ```
65+
fn fn_any_body(ident: &Ident, data: &Data) -> TokenStream {
66+
match data {
67+
Data::Struct(struct_data) => init_symbolic_item(ident, &struct_data.fields),
68+
Data::Enum(enum_data) => fn_any_enum(ident, enum_data),
69+
Data::Union(_) => {
70+
abort!(Span::call_site(), "Cannot derive `Arbitrary` for `{}` union", ident;
71+
note = ident.span() =>
72+
"`#[derive(Arbitrary)]` cannot be used for unions such as `{}`", ident
73+
)
74+
}
75+
}
76+
}
77+
78+
/// Generate an item initialization where an item can be a struct or a variant.
79+
/// For named fields, this will generate: `Item { field1: kani::any(), field2: kani::any(), .. }`
80+
/// For unnamed fields, this will generate: `Item (kani::any(), kani::any(), ..)`
81+
/// For unit field, generate an empty initialization.
82+
fn init_symbolic_item(ident: &Ident, fields: &Fields) -> TokenStream {
83+
match fields {
84+
Fields::Named(ref fields) => {
85+
// Use the span of each `syn::Field`. This way if one of the field types does not
86+
// implement `Arbitrary` then the compiler's error message underlines which field it
87+
// is. An example is shown in the readme of the parent directory.
88+
let init = fields.named.iter().map(|field| {
89+
let name = &field.ident;
90+
quote_spanned! {field.span()=>
91+
#name: kani::any()
92+
}
93+
});
94+
quote! {
95+
#ident {#( #init,)*}
96+
}
97+
}
98+
Fields::Unnamed(ref fields) => {
99+
// Expands to an expression like
100+
// Self(kani::any(), kani::any(), ..., kani::any());
101+
let init = fields.unnamed.iter().map(|field| {
102+
quote_spanned! {field.span()=>
103+
kani::any()
104+
}
105+
});
106+
quote! {
107+
#ident(#( #init,)*)
108+
}
109+
}
110+
Fields::Unit => {
111+
quote! {
112+
#ident
113+
}
114+
}
115+
}
116+
}
117+
118+
/// Generate the body of the function `any()` for enums. The cases are:
119+
/// 1. For zero-variants enumerations, this will encode a `panic!()` statement.
120+
/// 2. For one or more variants, the code will be something like:
121+
/// ```
122+
/// # enum Enum{
123+
/// # WithoutData,
124+
/// # WithUnNamedData(i32),
125+
/// # WithNamedData{ i: i32},
126+
/// # }
127+
/// #
128+
/// # impl kani::Arbitrary for Enum {
129+
/// # fn any() -> Self {
130+
/// match kani::any() {
131+
/// 0 => Enum::WithoutData,
132+
/// 1 => Enum::WithUnNamedData(kani::any()),
133+
/// _ => Enum::WithNamedData {i: kani::any()},
134+
/// }
135+
/// # }
136+
/// # }
137+
/// ```
138+
fn fn_any_enum(ident: &Ident, data: &DataEnum) -> TokenStream {
139+
if data.variants.is_empty() {
140+
let msg = format!(
141+
"Cannot create symbolic enum `{}`. Enums with zero-variants cannot be instantiated",
142+
ident
143+
);
144+
quote! {
145+
panic!(#msg)
146+
}
147+
} else {
148+
let arms = data.variants.iter().enumerate().map(|(idx, variant)| {
149+
let init = init_symbolic_item(&variant.ident, &variant.fields);
150+
if idx + 1 < data.variants.len() {
151+
let index = Index::from(idx);
152+
quote! {
153+
#index => #ident::#init,
154+
}
155+
} else {
156+
quote! {
157+
_ => #ident::#init,
158+
}
159+
}
160+
});
161+
162+
quote! {
163+
match kani::any() {
164+
#(#arms)*
165+
}
166+
}
167+
}
168+
}

library/kani_macros/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
// So we have to enable this on the commandline (see kani-rustc) with:
99
// RUSTFLAGS="-Zcrate-attr=feature(register_tool) -Zcrate-attr=register_tool(kanitool)"
1010

11+
mod derive;
12+
1113
// proc_macro::quote is nightly-only, so we'll cobble things together instead
1214
use proc_macro::TokenStream;
15+
use proc_macro_error::proc_macro_error;
1316
#[cfg(kani)]
1417
use {
1518
quote::quote,
@@ -148,3 +151,10 @@ pub fn stub(attr: TokenStream, item: TokenStream) -> TokenStream {
148151
result.extend(item);
149152
result
150153
}
154+
155+
/// Allow users to auto generate Arbitrary implementations by using `#[derive(Arbitrary)]` macro.
156+
#[proc_macro_error]
157+
#[proc_macro_derive(Arbitrary)]
158+
pub fn derive_arbitrary(item: TokenStream) -> TokenStream {
159+
derive::expand_derive_arbitrary(item)
160+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! Check that Kani can automatically derive an empty Arbitrary enum but the method `panic!()`
4+
//! when invoked since an empty enumeration cannot be instantiated.
5+
6+
#[derive(kani::Arbitrary)]
7+
enum Empty {}
8+
9+
#[kani::proof]
10+
fn check_no_variants() {
11+
let _e: Empty = kani::any();
12+
unreachable!();
13+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Checking harness check_no_variants...
2+
Failed Checks: Cannot create symbolic enum `Empty`. Enums with zero-variants cannot be instantiated
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+
//! Check that Kani can automatically derive Arbitrary for empty structs.
4+
5+
#[derive(kani::Arbitrary)]
6+
struct Void;
7+
8+
#[derive(kani::Arbitrary)]
9+
struct Void2(());
10+
11+
#[derive(kani::Arbitrary)]
12+
struct VoidOfVoid(Void, Void2);
13+
14+
#[kani::proof]
15+
fn check_arbitrary_point() {
16+
let _v: VoidOfVoid = kani::any();
17+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Checking harness check_arbitrary_point...
2+
VERIFICATION:- SUCCESSFUL
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! Check that Kani can automatically derive Arbitrary enums.
4+
//! An arbitrary enum should always generate a valid arbitrary variant.
5+
6+
extern crate kani;
7+
use kani::cover;
8+
9+
#[derive(kani::Arbitrary)]
10+
enum Wrapper {
11+
Empty,
12+
Bool(bool),
13+
Char { c: char },
14+
}
15+
16+
#[kani::proof]
17+
fn check_enum_wrapper() {
18+
match kani::any::<Wrapper>() {
19+
Wrapper::Empty => cover!(),
20+
Wrapper::Bool(b) => {
21+
cover!(b as u8 == 0);
22+
cover!(b as u8 == 1);
23+
assert!(b as u8 == 0 || b as u8 == 1);
24+
}
25+
Wrapper::Char { c } => {
26+
assert!(c <= char::MAX);
27+
cover!(c == 'a');
28+
cover!(c == '1');
29+
}
30+
}
31+
}
32+
33+
#[derive(kani::Arbitrary, Copy, Clone)]
34+
enum Comparison {
35+
Less = -1,
36+
Equal = 0,
37+
Greater = 1,
38+
}
39+
40+
#[kani::proof]
41+
fn check_comparison() {
42+
let comp: Comparison = kani::any();
43+
let disc = comp as i8;
44+
assert!(disc >= -1 && disc <= 1);
45+
match comp {
46+
Comparison::Less => assert!(disc == -1),
47+
Comparison::Equal => assert!(disc == 0),
48+
Comparison::Greater => assert!(disc == 1),
49+
}
50+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Checking harness check_comparison...
2+
SUCCESS\
3+
"assertion failed: disc >= -1 && disc <= 1"
4+
5+
SUCCESS\
6+
"assertion failed: disc == 1"
7+
8+
SUCCESS\
9+
"assertion failed: disc == -1"
10+
11+
SUCCESS\
12+
"assertion failed: disc == 0"
13+
14+
15+
Checking harness check_enum_wrapper...
16+
5 of 5 cover properties satisfied
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Checking harness check_arbitrary_point...
2+
6 of 6 cover properties satisfied
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! Check that Kani can automatically derive Arbitrary for structs with named fields.
4+
5+
extern crate kani;
6+
7+
use kani::cover;
8+
9+
#[derive(kani::Arbitrary)]
10+
struct Point<X, Y> {
11+
x: X,
12+
y: Y,
13+
}
14+
15+
#[kani::proof]
16+
fn check_arbitrary_point() {
17+
let point: Point<i32, i8> = kani::any();
18+
cover!(point.x > 0);
19+
cover!(point.x < 0);
20+
cover!(point.x == 0);
21+
cover!(point.y > 0);
22+
cover!(point.y < 0);
23+
cover!(point.y == 0);
24+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Checking harness check_arbitrary_point...
2+
4 of 4 cover properties satisfied
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! Check that Kani can automatically derive Arbitrary for structs with named fields.
4+
5+
#[derive(kani::Arbitrary)]
6+
struct Point {
7+
x: i32,
8+
y: i32,
9+
}
10+
11+
#[kani::proof]
12+
fn check_arbitrary_point() {
13+
let point: Point = kani::any();
14+
kani::cover!(point.x > 0);
15+
kani::cover!(point.x <= 0);
16+
kani::cover!(point.y > 0);
17+
kani::cover!(point.y <= 0);
18+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error[E0277]: the trait bound `NotArbitrary: kani::Arbitrary` is not satisfied
2+
|\
3+
| not_arbitrary: NotArbitrary,\
4+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `kani::Arbitrary` is not implemented for `NotArbitrary`
5+
6+
Error: "Failed to compile crate."
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! Check that there's a compilation error if user tries to use Arbitrary when one of the fields do
4+
//! not implement Arbitrary.
5+
6+
struct NotArbitrary(u8);
7+
8+
#[derive(kani::Arbitrary)]
9+
struct Arbitrary(u8);
10+
11+
#[derive(kani::Arbitrary)]
12+
struct Wrapper {
13+
arbitrary: Arbitrary,
14+
not_arbitrary: NotArbitrary,
15+
}
16+
17+
#[kani::proof]
18+
fn dead_harness() {
19+
panic!("This shouldn't compile");
20+
}

0 commit comments

Comments
 (0)