Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lifetime parameter to Arbitrary trait. Remove shrinking functionality. Implement Arbitrary for &str. #63

Merged
merged 11 commits into from
Nov 26, 2020
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ Released YYYY-MM-DD.

--------------------------------------------------------------------------------

## 1.0.0
frewsxcv marked this conversation as resolved.
Show resolved Hide resolved

Unreleased.

### Changed

* The `Arbitrary` trait now has a lifetime parameter. [#63](https://github.com/rust-fuzz/arbitrary/pull/63)
frewsxcv marked this conversation as resolved.
Show resolved Hide resolved

### Removed

* The `shrink` method on the `Arbitrary` trait has been removed. If you relied on the shrinking functionality, consider vendoring [this file](https://gist.github.com/frewsxcv/390976eb39c7e2a065c0b2d2731a3eb3) into your project.
frewsxcv marked this conversation as resolved.
Show resolved Hide resolved

--------------------------------------------------------------------------------

## 0.4.7

Released 2020-10-14.
Expand Down
31 changes: 2 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ pub struct Rgb {
pub b: u8,
}

impl Arbitrary for Rgb {
fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
impl<'a> Arbitrary<'a> for Rgb {
fn arbitrary(u: &'a mut Unstructured<'a>) -> Result<Self> {
frewsxcv marked this conversation as resolved.
Show resolved Hide resolved
let r = u8::arbitrary(u)?;
let g = u8::arbitrary(u)?;
let b = u8::arbitrary(u)?;
Expand All @@ -87,33 +87,6 @@ impl Arbitrary for Rgb {
}
```

### Shrinking

To assist with test case reduction, where you want to find the smallest and most
easily understandable test case that still demonstrates a bug you've discovered,
the `Arbitrary` trait has a `shrink` method. The `shrink` method returns an
iterator of "smaller" instances of `self`. The provided, default implementation
returns an empty iterator.

We can override the default for our `Rgb` struct above by shrinking each of its
components and then gluing them back together again:

```rust
impl Arbitrary for Rgb {
// ...

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let rs = self.r.shrink();
let gs = self.g.shrink();
let bs = self.b.shrink();
Box::new(rs.zip(gs).zip(bs).map(|((r, g), b)| Rgb { r, g, b }))
}
}
```

Note that deriving `Arbitrary` will automatically derive a custom `shrink`
implementation for you.

## License

Licensed under dual MIT or Apache-2.0 at your choice.
Expand Down
126 changes: 28 additions & 98 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,65 @@
extern crate proc_macro;

use proc_macro2::{Literal, TokenStream};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::*;

static ARBITRARY_LIFETIME_NAME: &str = "'arbitrary";

#[proc_macro_derive(Arbitrary)]
pub fn derive_arbitrary(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = syn::parse_macro_input!(tokens as syn::DeriveInput);
let arbitrary_lifetime =
LifetimeDef::new(Lifetime::new(ARBITRARY_LIFETIME_NAME, Span::call_site()));

let arbitrary_method = gen_arbitrary_method(&input);
let arbitrary_method = gen_arbitrary_method(&input, arbitrary_lifetime.clone());
let size_hint_method = gen_size_hint_method(&input);
let shrink_method = gen_shrink_method(&input);
let name = input.ident;
// Add a bound `T: Arbitrary` to every type parameter T.
let generics = add_trait_bounds(input.generics);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let generics = add_trait_bounds(input.generics, arbitrary_lifetime.clone());

// Build ImplGeneric with a lifetime (https://github.com/dtolnay/syn/issues/90)
let mut generics_with_lifetime = generics.clone();
generics_with_lifetime
.params
.push(GenericParam::Lifetime(arbitrary_lifetime.clone()));
let (impl_generics, _, _) = generics_with_lifetime.split_for_impl();

// Build TypeGenerics and WhereClause without a lifetime
let (_, ty_generics, where_clause) = generics.split_for_impl();

(quote! {
impl #impl_generics arbitrary::Arbitrary for #name #ty_generics #where_clause {
impl #impl_generics arbitrary::Arbitrary<#arbitrary_lifetime> for #name #ty_generics #where_clause {
#arbitrary_method
#size_hint_method
#shrink_method
}
})
.into()
}

// Add a bound `T: Arbitrary` to every type parameter T.
fn add_trait_bounds(mut generics: Generics) -> Generics {
fn add_trait_bounds(mut generics: Generics, lifetime: LifetimeDef) -> Generics {
for param in generics.params.iter_mut() {
if let GenericParam::Type(type_param) = param {
type_param.bounds.push(parse_quote!(arbitrary::Arbitrary));
type_param
.bounds
.push(parse_quote!(arbitrary::Arbitrary<#lifetime>));
}
}
generics
}

fn gen_arbitrary_method(input: &DeriveInput) -> TokenStream {
fn gen_arbitrary_method(input: &DeriveInput, lifetime: LifetimeDef) -> TokenStream {
let ident = &input.ident;
let arbitrary_structlike = |fields| {
let arbitrary = construct(fields, |_, _| quote!(arbitrary::Arbitrary::arbitrary(u)?));
let arbitrary_take_rest = construct_take_rest(fields);
quote! {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
fn arbitrary(u: &mut arbitrary::Unstructured<#lifetime>) -> arbitrary::Result<Self> {
Ok(#ident #arbitrary)
}

fn arbitrary_take_rest(mut u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
fn arbitrary_take_rest(mut u: arbitrary::Unstructured<#lifetime>) -> arbitrary::Result<Self> {
Ok(#ident #arbitrary_take_rest)
}
}
Expand All @@ -70,7 +84,7 @@ fn gen_arbitrary_method(input: &DeriveInput) -> TokenStream {
});
let count = data.variants.len() as u64;
quote! {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
fn arbitrary(u: &mut arbitrary::Unstructured<#lifetime>) -> arbitrary::Result<Self> {
// Use a multiply + shift to generate a ranged random number
// with slight bias. For details, see:
// https://lemire.me/blog/2016/06/30/fast-random-shuffling
Expand All @@ -80,7 +94,7 @@ fn gen_arbitrary_method(input: &DeriveInput) -> TokenStream {
})
}

fn arbitrary_take_rest(mut u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
fn arbitrary_take_rest(mut u: arbitrary::Unstructured<#lifetime>) -> arbitrary::Result<Self> {
// Use a multiply + shift to generate a ranged random number
// with slight bias. For details, see:
// https://lemire.me/blog/2016/06/30/fast-random-shuffling
Expand Down Expand Up @@ -162,87 +176,3 @@ fn gen_size_hint_method(input: &DeriveInput) -> TokenStream {
}
}
}

fn gen_shrink_method(input: &DeriveInput) -> TokenStream {
let ident = &input.ident;
let shrink_structlike = |fields| {
let inner = shrink(&quote!(#ident), fields, |i, field| match &field.ident {
Some(i) => quote!(&self.#i),
None => {
let i = Literal::usize_unsuffixed(i);
quote!(&self.#i)
}
});
quote! {
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
#inner
}
}
};

return match &input.data {
Data::Struct(data) => shrink_structlike(&data.fields),
Data::Union(data) => shrink_structlike(&Fields::Named(data.fields.clone())),
Data::Enum(data) => {
let variants = data.variants.iter().map(|variant| {
let mut binding_names = Vec::new();
let bindings = match &variant.fields {
Fields::Named(_) => {
let names = variant.fields.iter().map(|f| {
let name = f.ident.as_ref().unwrap();
binding_names.push(quote!(#name));
name
});
quote!({#(#names),*})
}
Fields::Unnamed(_) => {
let names = (0..variant.fields.len()).map(|i| {
let name = quote::format_ident!("f{}", i);
binding_names.push(quote!(#name));
name
});
quote!((#(#names),*))
}
Fields::Unit => quote!(),
};
let variant_name = &variant.ident;
let shrink = shrink(&quote!(#ident::#variant_name), &variant.fields, |i, _| {
binding_names[i].clone()
});
quote!(#ident::#variant_name #bindings => { #shrink })
});
quote! {
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
match self {
#(#variants)*
}
}
}
}
};

fn shrink(
prefix: &TokenStream,
fields: &Fields,
access_field: impl Fn(usize, &Field) -> TokenStream,
) -> TokenStream {
if fields.len() == 0 {
return quote!(Box::new(None.into_iter()));
}
let iters = fields.iter().enumerate().map(|(i, f)| {
let name = quote::format_ident!("field{}", i);
let field = access_field(i, f);
quote! { let mut #name = arbitrary::Arbitrary::shrink(#field); }
});
let ctor = construct(fields, |i, _| {
let iter = quote::format_ident!("field{}", i);
quote!(#iter.next()?)
});
quote! {
#(#iters)*
Box::new(std::iter::from_fn(move || {
Some(#prefix #ctor)
}))
}
}
}
Loading