Skip to content

Commit

Permalink
Add faillible version of size_hint to properly handle recursive struc…
Browse files Browse the repository at this point in the history
…tures

The default implementation forwards to the current size_hint, so that
it can be relied upon also for structures that still use the default implementation.

The default implementation of `size_hint` is not changed, so that it does not break existing
implementations
  • Loading branch information
sosthene-nitrokey committed Jul 23, 2024
1 parent 8fff98e commit b5e2b9c
Show file tree
Hide file tree
Showing 5 changed files with 510 additions and 102 deletions.
35 changes: 22 additions & 13 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,27 +335,27 @@ fn gen_size_hint_method(input: &DeriveInput) -> Result<TokenStream> {
determine_field_constructor(f).map(|field_constructor| {
match field_constructor {
FieldConstructor::Default | FieldConstructor::Value(_) => {
quote!((0, Some(0)))
quote!(Ok((0, Some(0))))
}
FieldConstructor::Arbitrary => {
quote! { <#ty as arbitrary::Arbitrary>::size_hint(depth) }
quote! { <#ty as arbitrary::Arbitrary>::faillible_size_hint(depth) }
}

// Note that in this case it's hard to determine what size_hint must be, so size_of::<T>() is
// just an educated guess, although it's gonna be inaccurate for dynamically
// allocated types (Vec, HashMap, etc.).
FieldConstructor::With(_) => {
quote! { (::core::mem::size_of::<#ty>(), None) }
quote! { Ok((::core::mem::size_of::<#ty>(), None)) }
}
}
})
})
.collect::<Result<Vec<TokenStream>>>()
.map(|hints| {
quote! {
arbitrary::size_hint::and_all(&[
#( #hints ),*
])
Ok(arbitrary::size_hint::and_all(&[
#( #hints? ),*
]))
}
})
};
Expand All @@ -364,7 +364,12 @@ fn gen_size_hint_method(input: &DeriveInput) -> Result<TokenStream> {
quote! {
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::recursion_guard(depth, |depth| #hint)
Self::faillible_size_hint(depth).unwrap_or_default()
}

#[inline]
fn faillible_size_hint(depth: usize) -> Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
arbitrary::size_hint::faillible_recursion_guard(depth, |depth| #hint)
}
}
})
Expand All @@ -381,12 +386,16 @@ fn gen_size_hint_method(input: &DeriveInput) -> Result<TokenStream> {
quote! {
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and(
<u32 as arbitrary::Arbitrary>::size_hint(depth),
arbitrary::size_hint::recursion_guard(depth, |depth| {
arbitrary::size_hint::or_all(&[ #( #variants ),* ])
}),
)
Self::faillible_size_hint(depth).unwrap_or_default()
}
#[inline]
fn faillible_size_hint(depth: usize) -> Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
Ok(arbitrary::size_hint::and(
<u32 as arbitrary::Arbitrary>::faillible_size_hint(depth)?,
arbitrary::size_hint::faillible_recursion_guard(depth, |depth| {
Ok(arbitrary::size_hint::or_all(&[ #( #variants? ),* ]))
})?,
))
}
}
}),
Expand Down
23 changes: 23 additions & 0 deletions src/faillible_size_hint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! Utilities for working with and combining the results of
//! [`Arbitrary::faillible_size_hint`][crate::Arbitrary::faillible_size_hint].

use crate::{size_hint::MAX_DEPTH, MaxRecursionReached};

/// Protects against potential infinite recursion when calculating size hints
/// due to indirect type recursion.
///
/// When the depth is not too deep, calls `f` with `depth + 1` to calculate the
/// size hint.
///
/// Otherwise, returns an error.
#[inline]
pub fn recursion_guard(
depth: usize,
f: impl FnOnce(usize) -> Result<(usize, Option<usize>), MaxRecursionReached>,
) -> Result<(usize, Option<usize>), MaxRecursionReached> {
if depth > MAX_DEPTH {
Err(MaxRecursionReached)
} else {
f(depth + 1)
}
}
Loading

0 comments on commit b5e2b9c

Please sign in to comment.