|
| 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 | +} |
0 commit comments