From 2a88aa9d911a3aabd6a70cd6a4db012cd7b2304d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 7 Apr 2025 14:13:56 +1000 Subject: [PATCH 1/7] Add `no_std` Support --- .github/workflows/rust.yml | 9 +++++ Cargo.toml | 16 +++++++++ derive/src/lib.rs | 2 ++ src/error.rs | 16 ++++++--- src/foreign/alloc/borrow.rs | 2 +- src/foreign/alloc/boxed.rs | 2 +- src/foreign/alloc/collections/binary_heap.rs | 2 +- src/foreign/alloc/collections/btree_map.rs | 2 +- src/foreign/alloc/collections/btree_set.rs | 2 +- src/foreign/alloc/collections/linked_list.rs | 2 +- src/foreign/alloc/collections/vec_deque.rs | 2 +- src/foreign/alloc/ffi/c_str.rs | 8 ++++- src/foreign/alloc/ffi/mod.rs | 1 + src/foreign/alloc/mod.rs | 1 + src/foreign/alloc/rc.rs | 2 +- src/foreign/alloc/string.rs | 2 +- src/foreign/alloc/sync.rs | 2 +- src/foreign/alloc/vec.rs | 2 +- src/foreign/core/mod.rs | 2 ++ src/foreign/{std => core}/net.rs | 11 +++--- src/foreign/mod.rs | 2 ++ src/foreign/std/collections/hash_map.rs | 6 ++-- src/foreign/std/collections/hash_set.rs | 6 ++-- src/foreign/std/ffi/os_str.rs | 1 + src/foreign/std/mod.rs | 1 - src/lib.rs | 15 ++++++-- src/size_hint.rs | 4 +-- src/tests.rs | 36 ++++++++++++++++++-- src/unstructured.rs | 31 ++++++++++------- 29 files changed, 142 insertions(+), 48 deletions(-) rename src/foreign/{std => core}/net.rs (88%) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1dbac58..7829121 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -33,6 +33,15 @@ jobs: - run: rustup toolchain add 1.63 - name: Build run: cargo +1.63 check --lib --all-features + check_no_std: + name: Check no_std + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: rustup update + - run: rustup toolchain add 1.82 + - run: rustup target add x86_64-unknown-none + - run: cargo +1.82 check --no-default-features --features alloc,derive,core_error,core_net,alloc_ffi_cstring --target x86_64-unknown-none clippy: name: Clippy runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 7902497..a508f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,19 @@ rust-version = "1.63.0" # Keep in sync with version documented in the README.md derive_arbitrary = { version = "~1.4.0", path = "./derive", optional = true } [features] +default = ["std"] # Turn this feature on to enable support for `#[derive(Arbitrary)]`. derive = ["derive_arbitrary"] +# Enables support for the `std` crate. +std = ["alloc"] +# Enables support for the `alloc` crate. +alloc = [] +# Enables using core::error::Error. Increases MSRV to at least 1.81.0 +core_error = [] +# Enables using core::net when `std` is disabled. Increases MSRV to at least 1.77.0 +core_net = [] +# Enables using `alloc::ffi::CString` when `std` is disabled. Increases MSRV to at least 1.64.0 +alloc_ffi_cstring = ["alloc"] [[example]] name = "derive_enum" @@ -40,3 +51,8 @@ members = ["./fuzz"] [dev-dependencies] exhaustigen = "0.1.0" + +[lints.clippy] +alloc_instead_of_core = "warn" +std_instead_of_alloc = "warn" +std_instead_of_core = "warn" diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 81c0274..115be1d 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -58,6 +58,8 @@ fn expand_derive_arbitrary(input: syn::DeriveInput) -> Result { Ok(quote! { const _: () = { + extern crate std; + ::std::thread_local! { #[allow(non_upper_case_globals)] static #recursive_count: ::core::cell::Cell = ::core::cell::Cell::new(0); diff --git a/src/error.rs b/src/error.rs index 6ca8f19..07af97a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use std::{error, fmt}; +use core::fmt; /// An enumeration of buffer creation errors #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -32,20 +32,28 @@ impl fmt::Display for Error { } } -impl error::Error for Error {} +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +#[cfg(all(not(feature = "std"), feature = "core_error"))] +impl core::error::Error for Error {} /// A `Result` with the error type fixed as `arbitrary::Error`. /// /// Either an `Ok(T)` or `Err(arbitrary::Error)`. -pub type Result = std::result::Result; +pub type Result = core::result::Result; #[cfg(test)] mod tests { + #[cfg(feature = "alloc")] + use alloc::string::String; + // Often people will import our custom `Result` type because 99.9% of // results in a file will be `arbitrary::Result` but then have that one last // 0.1% that want to have a custom error type. Don't make them prefix that - // 0.1% as `std::result::Result`; instead, let `arbitrary::Result` have an + // 0.1% as `core::result::Result`; instead, let `arbitrary::Result` have an // overridable error type. + #[cfg(feature = "alloc")] #[test] fn can_use_custom_error_types_with_result() -> super::Result<(), String> { Ok(()) diff --git a/src/foreign/alloc/borrow.rs b/src/foreign/alloc/borrow.rs index b5b1e22..617afcb 100644 --- a/src/foreign/alloc/borrow.rs +++ b/src/foreign/alloc/borrow.rs @@ -1,6 +1,6 @@ use { crate::{size_hint, Arbitrary, Result, Unstructured}, - std::borrow::{Cow, ToOwned}, + alloc::borrow::{Cow, ToOwned}, }; impl<'a, A> Arbitrary<'a> for Cow<'a, A> diff --git a/src/foreign/alloc/boxed.rs b/src/foreign/alloc/boxed.rs index c3014a3..f397557 100644 --- a/src/foreign/alloc/boxed.rs +++ b/src/foreign/alloc/boxed.rs @@ -1,6 +1,6 @@ use { crate::{size_hint, Arbitrary, Result, Unstructured}, - std::boxed::Box, + alloc::{boxed::Box, string::String}, }; impl<'a, A> Arbitrary<'a> for Box diff --git a/src/foreign/alloc/collections/binary_heap.rs b/src/foreign/alloc/collections/binary_heap.rs index 25a0384..02ef3fc 100644 --- a/src/foreign/alloc/collections/binary_heap.rs +++ b/src/foreign/alloc/collections/binary_heap.rs @@ -1,6 +1,6 @@ use { crate::{Arbitrary, Result, Unstructured}, - std::collections::binary_heap::BinaryHeap, + alloc::collections::binary_heap::BinaryHeap, }; impl<'a, A> Arbitrary<'a> for BinaryHeap diff --git a/src/foreign/alloc/collections/btree_map.rs b/src/foreign/alloc/collections/btree_map.rs index 21b93a4..65790c1 100644 --- a/src/foreign/alloc/collections/btree_map.rs +++ b/src/foreign/alloc/collections/btree_map.rs @@ -1,6 +1,6 @@ use { crate::{Arbitrary, Result, Unstructured}, - std::collections::btree_map::BTreeMap, + alloc::collections::btree_map::BTreeMap, }; impl<'a, K, V> Arbitrary<'a> for BTreeMap diff --git a/src/foreign/alloc/collections/btree_set.rs b/src/foreign/alloc/collections/btree_set.rs index 8c6e92f..b862a0f 100644 --- a/src/foreign/alloc/collections/btree_set.rs +++ b/src/foreign/alloc/collections/btree_set.rs @@ -1,6 +1,6 @@ use { crate::{Arbitrary, Result, Unstructured}, - std::collections::btree_set::BTreeSet, + alloc::collections::btree_set::BTreeSet, }; impl<'a, A> Arbitrary<'a> for BTreeSet diff --git a/src/foreign/alloc/collections/linked_list.rs b/src/foreign/alloc/collections/linked_list.rs index 6bf2e98..7bb6425 100644 --- a/src/foreign/alloc/collections/linked_list.rs +++ b/src/foreign/alloc/collections/linked_list.rs @@ -1,6 +1,6 @@ use { crate::{Arbitrary, Result, Unstructured}, - std::collections::linked_list::LinkedList, + alloc::collections::linked_list::LinkedList, }; impl<'a, A> Arbitrary<'a> for LinkedList diff --git a/src/foreign/alloc/collections/vec_deque.rs b/src/foreign/alloc/collections/vec_deque.rs index 40e0413..44a37b6 100644 --- a/src/foreign/alloc/collections/vec_deque.rs +++ b/src/foreign/alloc/collections/vec_deque.rs @@ -1,6 +1,6 @@ use { crate::{Arbitrary, Result, Unstructured}, - std::collections::vec_deque::VecDeque, + alloc::collections::vec_deque::VecDeque, }; impl<'a, A> Arbitrary<'a> for VecDeque diff --git a/src/foreign/alloc/ffi/c_str.rs b/src/foreign/alloc/ffi/c_str.rs index a1b2383..c704170 100644 --- a/src/foreign/alloc/ffi/c_str.rs +++ b/src/foreign/alloc/ffi/c_str.rs @@ -1,6 +1,12 @@ +#[cfg(feature = "std")] +use std::ffi::CString; + +#[cfg(all(not(feature = "std"), feature = "alloc_ffi_cstring"))] +use alloc::ffi::CString; + use { crate::{Arbitrary, Result, Unstructured}, - std::ffi::CString, + alloc::vec::Vec, }; impl<'a> Arbitrary<'a> for CString { diff --git a/src/foreign/alloc/ffi/mod.rs b/src/foreign/alloc/ffi/mod.rs index 9238cfc..a170589 100644 --- a/src/foreign/alloc/ffi/mod.rs +++ b/src/foreign/alloc/ffi/mod.rs @@ -1 +1,2 @@ +#[cfg(any(feature = "std", feature = "alloc_ffi_cstring"))] mod c_str; diff --git a/src/foreign/alloc/mod.rs b/src/foreign/alloc/mod.rs index a04b62c..1296385 100644 --- a/src/foreign/alloc/mod.rs +++ b/src/foreign/alloc/mod.rs @@ -9,5 +9,6 @@ mod collections; mod ffi; mod rc; mod string; +#[cfg(target_has_atomic = "ptr")] mod sync; mod vec; diff --git a/src/foreign/alloc/rc.rs b/src/foreign/alloc/rc.rs index 6d58167..2243be4 100644 --- a/src/foreign/alloc/rc.rs +++ b/src/foreign/alloc/rc.rs @@ -1,6 +1,6 @@ use { crate::{size_hint, Arbitrary, Result, Unstructured}, - std::rc::Rc, + alloc::rc::Rc, }; impl<'a, A> Arbitrary<'a> for Rc diff --git a/src/foreign/alloc/string.rs b/src/foreign/alloc/string.rs index a579784..47ea9eb 100644 --- a/src/foreign/alloc/string.rs +++ b/src/foreign/alloc/string.rs @@ -1,6 +1,6 @@ use { crate::{Arbitrary, Result, Unstructured}, - std::string::String, + alloc::string::String, }; impl<'a> Arbitrary<'a> for String { diff --git a/src/foreign/alloc/sync.rs b/src/foreign/alloc/sync.rs index c8ca1db..64d2dcd 100644 --- a/src/foreign/alloc/sync.rs +++ b/src/foreign/alloc/sync.rs @@ -1,6 +1,6 @@ use { crate::{size_hint, Arbitrary, Result, Unstructured}, - std::sync::Arc, + alloc::sync::Arc, }; impl<'a, A> Arbitrary<'a> for Arc diff --git a/src/foreign/alloc/vec.rs b/src/foreign/alloc/vec.rs index 63313ba..2985712 100644 --- a/src/foreign/alloc/vec.rs +++ b/src/foreign/alloc/vec.rs @@ -1,6 +1,6 @@ use { crate::{Arbitrary, Result, Unstructured}, - std::vec::Vec, + alloc::vec::Vec, }; impl<'a, A> Arbitrary<'a> for Vec diff --git a/src/foreign/core/mod.rs b/src/foreign/core/mod.rs index 60089e9..17a819a 100644 --- a/src/foreign/core/mod.rs +++ b/src/foreign/core/mod.rs @@ -9,6 +9,8 @@ mod char; mod cmp; mod iter; mod marker; +#[cfg(any(feature = "std", feature = "core_net"))] +mod net; mod num; mod ops; mod option; diff --git a/src/foreign/std/net.rs b/src/foreign/core/net.rs similarity index 88% rename from src/foreign/std/net.rs rename to src/foreign/core/net.rs index 41e1f8a..9ea0210 100644 --- a/src/foreign/std/net.rs +++ b/src/foreign/core/net.rs @@ -1,7 +1,10 @@ -use { - crate::{size_hint, Arbitrary, Result, Unstructured}, - std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, -}; +#[cfg(feature = "std")] +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; + +#[cfg(all(not(feature = "std"), feature = "core_net"))] +use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; + +use crate::{size_hint, Arbitrary, Result, Unstructured}; impl<'a> Arbitrary<'a> for Ipv4Addr { fn arbitrary(u: &mut Unstructured<'a>) -> Result { diff --git a/src/foreign/mod.rs b/src/foreign/mod.rs index b1c42be..3868936 100644 --- a/src/foreign/mod.rs +++ b/src/foreign/mod.rs @@ -2,6 +2,8 @@ //! //! [`Arbitrary`]: crate::Arbitrary +#[cfg(feature = "alloc")] mod alloc; mod core; +#[cfg(feature = "std")] mod std; diff --git a/src/foreign/std/collections/hash_map.rs b/src/foreign/std/collections/hash_map.rs index d2e77af..85afe3a 100644 --- a/src/foreign/std/collections/hash_map.rs +++ b/src/foreign/std/collections/hash_map.rs @@ -1,9 +1,7 @@ use { crate::{Arbitrary, Result, Unstructured}, - std::{ - collections::hash_map::HashMap, - hash::{BuildHasher, Hash}, - }, + core::hash::{BuildHasher, Hash}, + std::collections::hash_map::HashMap, }; impl<'a, K, V, S> Arbitrary<'a> for HashMap diff --git a/src/foreign/std/collections/hash_set.rs b/src/foreign/std/collections/hash_set.rs index 5cb63d2..07c4c8a 100644 --- a/src/foreign/std/collections/hash_set.rs +++ b/src/foreign/std/collections/hash_set.rs @@ -1,9 +1,7 @@ use { crate::{Arbitrary, Result, Unstructured}, - std::{ - collections::hash_set::HashSet, - hash::{BuildHasher, Hash}, - }, + core::hash::{BuildHasher, Hash}, + std::collections::hash_set::HashSet, }; impl<'a, A, S> Arbitrary<'a> for HashSet diff --git a/src/foreign/std/ffi/os_str.rs b/src/foreign/std/ffi/os_str.rs index 9fa0cb3..83a3b27 100644 --- a/src/foreign/std/ffi/os_str.rs +++ b/src/foreign/std/ffi/os_str.rs @@ -1,5 +1,6 @@ use { crate::{Arbitrary, Result, Unstructured}, + alloc::string::String, std::ffi::OsString, }; diff --git a/src/foreign/std/mod.rs b/src/foreign/std/mod.rs index bc85aa2..7731262 100644 --- a/src/foreign/std/mod.rs +++ b/src/foreign/std/mod.rs @@ -5,6 +5,5 @@ mod collections; mod ffi; -mod net; mod path; mod sync; diff --git a/src/lib.rs b/src/lib.rs index 2535d3a..d2518bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,13 @@ #![deny(rust_2018_compatibility)] #![deny(rust_2018_idioms)] #![deny(unused)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; + +#[cfg(feature = "alloc")] +extern crate alloc; mod error; mod foreign; @@ -49,8 +56,12 @@ impl core::fmt::Display for MaxRecursionReached { } } +#[cfg(feature = "std")] impl std::error::Error for MaxRecursionReached {} +#[cfg(all(not(feature = "std"), feature = "core_error"))] +impl core::error::Error for MaxRecursionReached {} + /// Generate arbitrary structured values from raw, unstructured data. /// /// The `Arbitrary` trait allows you to generate valid structured values, like @@ -122,9 +133,9 @@ impl std::error::Error for MaxRecursionReached {} /// /// ``` /// # #[cfg(feature = "derive")] mod foo { -/// # pub struct MyCollection { _t: std::marker::PhantomData } +/// # pub struct MyCollection { _t: core::marker::PhantomData } /// # impl MyCollection { -/// # pub fn new() -> Self { MyCollection { _t: std::marker::PhantomData } } +/// # pub fn new() -> Self { MyCollection { _t: core::marker::PhantomData } } /// # pub fn insert(&mut self, element: T) {} /// # } /// use arbitrary::{Arbitrary, Result, Unstructured}; diff --git a/src/size_hint.rs b/src/size_hint.rs index 95707ee..9f73346 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -67,10 +67,10 @@ pub fn and_all(hints: &[(usize, Option)]) -> (usize, Option) { /// `lhs` and `rhs` size hints. #[inline] pub fn or(lhs: (usize, Option), rhs: (usize, Option)) -> (usize, Option) { - let lower = std::cmp::min(lhs.0, rhs.0); + let lower = core::cmp::min(lhs.0, rhs.0); let upper = lhs .1 - .and_then(|lhs| rhs.1.map(|rhs| std::cmp::max(lhs, rhs))); + .and_then(|lhs| rhs.1.map(|rhs| core::cmp::max(lhs, rhs))); (lower, upper) } diff --git a/src/tests.rs b/src/tests.rs index 7746715..0fc8984 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,12 +1,22 @@ +use super::{Arbitrary, Result, Unstructured}; + +#[cfg(feature = "std")] use { - super::{Arbitrary, Result, Unstructured}, - std::{collections::HashSet, fmt::Debug, hash::Hash, rc::Rc, sync::Arc}, + core::{fmt::Debug, hash::Hash}, + std::collections::HashSet, }; +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, rc::Rc, string::String, vec, vec::Vec}; + +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] +use alloc::sync::Arc; + /// Assert that the given expected values are all generated. /// /// Exhaustively enumerates all buffers up to length 10 containing the /// following bytes: `0x00`, `0x01`, `0x61` (aka ASCII 'a'), and `0xff` +#[cfg(feature = "std")] fn assert_generates(expected_values: impl IntoIterator) where T: Clone + Debug + Hash + Eq + for<'a> Arbitrary<'a>, @@ -26,7 +36,7 @@ where buf.clear(); buf.extend( - std::iter::repeat_with(|| { + core::iter::repeat_with(|| { let index = g.gen(bytes.len() - 1); bytes[index] }) @@ -116,6 +126,7 @@ fn finite_buffer_fill_buffer() { assert_eq!(z, [0, 0]); } +#[cfg(feature = "std")] #[test] fn arbitrary_for_integers() { let x = [1, 2, 3, 4]; @@ -153,6 +164,7 @@ fn arbitrary_take_rest_for_bytes() { assert_eq!(expected, actual); } +#[cfg(feature = "std")] #[test] fn arbitrary_for_vec_u8() { assert_generates::>([ @@ -174,6 +186,7 @@ fn arbitrary_for_vec_u8() { ]); } +#[cfg(feature = "std")] #[test] fn arbitrary_for_vec_vec_u8() { assert_generates::>>([ @@ -192,6 +205,7 @@ fn arbitrary_for_vec_vec_u8() { ]); } +#[cfg(feature = "std")] #[test] fn arbitrary_for_vec_vec_vec_u8() { assert_generates::>>>([ @@ -216,6 +230,7 @@ fn arbitrary_for_vec_vec_vec_u8() { ]); } +#[cfg(feature = "std")] #[test] fn arbitrary_for_string() { assert_generates::(["".into(), "a".into(), "aa".into(), "aaa".into()]); @@ -230,26 +245,32 @@ fn arbitrary_collection() { checked_arbitrary::<&[u8]>(&mut Unstructured::new(&x)).unwrap(), &[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3] ); + #[cfg(feature = "alloc")] assert_eq!( checked_arbitrary::>(&mut Unstructured::new(&x)).unwrap(), &[2, 4, 6, 8, 1] ); + #[cfg(feature = "alloc")] assert_eq!( &*checked_arbitrary::>(&mut Unstructured::new(&x)).unwrap(), &[2, 4, 6, 8, 1] ); + #[cfg(feature = "alloc")] assert_eq!( &*checked_arbitrary::>(&mut Unstructured::new(&x)).unwrap(), &[2, 4, 6, 8, 1] ); + #[cfg(feature = "alloc")] assert_eq!( &*checked_arbitrary::>(&mut Unstructured::new(&x)).unwrap(), &[2, 4, 6, 8, 1] ); + #[cfg(feature = "alloc")] assert_eq!( checked_arbitrary::>(&mut Unstructured::new(&x)).unwrap(), &[84148994] ); + #[cfg(feature = "alloc")] assert_eq!( checked_arbitrary::(&mut Unstructured::new(&x)).unwrap(), "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x01\x02\x03" @@ -264,26 +285,32 @@ fn arbitrary_take_rest() { checked_arbitrary_take_rest::<&[u8]>(Unstructured::new(&x)).unwrap(), &[1, 2, 3, 4] ); + #[cfg(feature = "alloc")] assert_eq!( checked_arbitrary_take_rest::>(Unstructured::new(&x)).unwrap(), &[2, 4] ); + #[cfg(feature = "alloc")] assert_eq!( &*checked_arbitrary_take_rest::>(Unstructured::new(&x)).unwrap(), &[2, 4] ); + #[cfg(feature = "alloc")] assert_eq!( &*checked_arbitrary_take_rest::>(Unstructured::new(&x)).unwrap(), &[2, 4] ); + #[cfg(feature = "alloc")] assert_eq!( &*checked_arbitrary_take_rest::>(Unstructured::new(&x)).unwrap(), &[2, 4] ); + #[cfg(feature = "alloc")] assert_eq!( checked_arbitrary_take_rest::>(Unstructured::new(&x)).unwrap(), &[0x040302] ); + #[cfg(feature = "alloc")] assert_eq!( checked_arbitrary_take_rest::(Unstructured::new(&x)).unwrap(), "\x01\x02\x03\x04" @@ -294,18 +321,21 @@ fn arbitrary_take_rest() { checked_arbitrary_take_rest::<&[u8]>(Unstructured::new(&[])).unwrap(), &[] ); + #[cfg(feature = "alloc")] assert_eq!( checked_arbitrary_take_rest::>(Unstructured::new(&[])).unwrap(), &[] ); // Cannot consume all but can consume part of the input + #[cfg(feature = "alloc")] assert_eq!( checked_arbitrary_take_rest::(Unstructured::new(&[1, 0xFF, 2])).unwrap(), "\x01" ); } +#[cfg(feature = "alloc")] #[test] fn size_hint_for_tuples() { assert_eq!( diff --git a/src/unstructured.rs b/src/unstructured.rs index b31f523..dfdb226 100644 --- a/src/unstructured.rs +++ b/src/unstructured.rs @@ -9,9 +9,9 @@ //! Wrappers around raw, unstructured bytes. use crate::{Arbitrary, Error, Result}; -use std::marker::PhantomData; -use std::ops::ControlFlow; -use std::{mem, ops}; +use core::marker::PhantomData; +use core::ops::ControlFlow; +use core::{mem, ops}; /// A source of unstructured data. /// @@ -186,9 +186,9 @@ impl<'a> Unstructured<'a> { /// /// ``` /// use arbitrary::{Arbitrary, Result, Unstructured}; - /// # pub struct MyCollection { _t: std::marker::PhantomData } + /// # pub struct MyCollection { _t: core::marker::PhantomData } /// # impl MyCollection { - /// # pub fn with_capacity(capacity: usize) -> Self { MyCollection { _t: std::marker::PhantomData } } + /// # pub fn with_capacity(capacity: usize) -> Self { MyCollection { _t: core::marker::PhantomData } } /// # pub fn insert(&mut self, element: T) {} /// # } /// @@ -218,7 +218,7 @@ impl<'a> Unstructured<'a> { let byte_size = self.arbitrary_byte_size()?; let (lower, upper) = ::size_hint(0); let elem_size = upper.unwrap_or(lower * 2); - let elem_size = std::cmp::max(1, elem_size); + let elem_size = core::cmp::max(1, elem_size); Ok(byte_size / elem_size) } @@ -424,7 +424,9 @@ impl<'a> Unstructured<'a> { /// Selecting a random item from a set: /// /// ``` - /// use std::collections::BTreeSet; + /// # #[cfg(feature = "std")] + /// # { + /// use alloc::collections::BTreeSet; /// use arbitrary::Unstructured; /// /// let mut u = Unstructured::new(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); @@ -433,6 +435,7 @@ impl<'a> Unstructured<'a> { /// let choice = u.choose_iter(set.iter()).unwrap(); /// /// println!("chose {}", choice); + /// # } /// ``` pub fn choose_iter(&mut self, choices: I) -> Result where @@ -556,7 +559,7 @@ impl<'a> Unstructured<'a> { /// assert_eq!(buf, [0, 0]); /// ``` pub fn fill_buffer(&mut self, buffer: &mut [u8]) -> Result<()> { - let n = std::cmp::min(buffer.len(), self.data.len()); + let n = core::cmp::min(buffer.len(), self.data.len()); buffer[..n].copy_from_slice(&self.data[..n]); for byte in buffer[n..].iter_mut() { *byte = 0; @@ -672,8 +675,8 @@ impl<'a> Unstructured<'a> { /// times the function is called. /// /// You may break out of the loop early by returning - /// `Ok(std::ops::ControlFlow::Break)`. To continue the loop, return - /// `Ok(std::ops::ControlFlow::Continue)`. + /// `Ok(core::ops::ControlFlow::Break)`. To continue the loop, return + /// `Ok(core::ops::ControlFlow::Continue)`. /// /// # Panics /// @@ -686,7 +689,7 @@ impl<'a> Unstructured<'a> { /// /// ``` /// use arbitrary::{Result, Unstructured}; - /// use std::ops::ControlFlow; + /// use core::ops::ControlFlow; /// /// enum Type { /// /// A boolean type. @@ -800,7 +803,7 @@ impl<'a, ElementType: Arbitrary<'a>> Iterator for ArbitraryTakeRestIter<'a, Elem /// Don't implement this trait yourself. pub trait Int: Copy - + std::fmt::Debug + + core::fmt::Debug + PartialOrd + Ord + ops::Sub @@ -904,8 +907,12 @@ impl_int! { #[cfg(test)] mod tests { + #[cfg(feature = "alloc")] + use alloc::vec; + use super::*; + #[cfg(feature = "alloc")] #[test] fn test_byte_size() { let mut u = Unstructured::new(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 6]); From 22358b99f68758d48d9a29f2822db0d3f45da9e9 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 8 Apr 2025 14:26:22 +1000 Subject: [PATCH 2/7] Specify toolchain when adding target --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7829121..aa8c13c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -40,7 +40,7 @@ jobs: - uses: actions/checkout@v3 - run: rustup update - run: rustup toolchain add 1.82 - - run: rustup target add x86_64-unknown-none + - run: rustup target add x86_64-unknown-none --toolchain 1.82 - run: cargo +1.82 check --no-default-features --features alloc,derive,core_error,core_net,alloc_ffi_cstring --target x86_64-unknown-none clippy: name: Clippy From 090863861ded00e64ffaeed0a7ebb25f2feb8160 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 8 Apr 2025 14:28:02 +1000 Subject: [PATCH 3/7] Fix failing test --- src/unstructured.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/unstructured.rs b/src/unstructured.rs index dfdb226..db03b60 100644 --- a/src/unstructured.rs +++ b/src/unstructured.rs @@ -426,6 +426,7 @@ impl<'a> Unstructured<'a> { /// ``` /// # #[cfg(feature = "std")] /// # { + /// # extern crate alloc; /// use alloc::collections::BTreeSet; /// use arbitrary::Unstructured; /// From 5550858f05a3299a561db4aab2de1198f58e1a4e Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 8 Apr 2025 18:44:08 +1000 Subject: [PATCH 4/7] Feature gate recursion count Allows the `derive` feature in `no_std` --- Cargo.toml | 4 ++- derive/Cargo.toml | 4 +++ derive/src/lib.rs | 88 ++++++++++++++++++++++++++++++++++++----------- src/tests.rs | 3 +- tests/derive.rs | 1 + 5 files changed, 78 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a508f71..2e9e1d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ rust-version = "1.63.0" # Keep in sync with version documented in the README.md derive_arbitrary = { version = "~1.4.0", path = "./derive", optional = true } [features] -default = ["std"] +default = ["std", "recursive_count"] # Turn this feature on to enable support for `#[derive(Arbitrary)]`. derive = ["derive_arbitrary"] # Enables support for the `std` crate. @@ -36,6 +36,8 @@ core_error = [] core_net = [] # Enables using `alloc::ffi::CString` when `std` is disabled. Increases MSRV to at least 1.64.0 alloc_ffi_cstring = ["alloc"] +# Checks for unbounded recursion at runtime. This does nothing without the "derive" feature. +recursive_count = ["derive_arbitrary?/recursive_count"] [[example]] name = "derive_enum" diff --git a/derive/Cargo.toml b/derive/Cargo.toml index c92a941..8c31dda 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -25,3 +25,7 @@ syn = { version = "2", features = ['derive', 'parsing', 'extra-traits'] } [lib] proc-macro = true + +[features] +# Checks for unbounded recursion at runtime. Requires `std`. +recursive_count = [] diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 115be1d..678b269 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -29,13 +29,18 @@ fn expand_derive_arbitrary(input: syn::DeriveInput) -> Result { let (lifetime_without_bounds, lifetime_with_bounds) = build_arbitrary_lifetime(input.generics.clone()); + #[cfg(feature = "recursive_count")] let recursive_count = syn::Ident::new( &format!("RECURSIVE_COUNT_{}", input.ident), Span::call_site(), ); - let arbitrary_method = - gen_arbitrary_method(&input, lifetime_without_bounds.clone(), &recursive_count)?; + let arbitrary_method = gen_arbitrary_method( + &input, + lifetime_without_bounds.clone(), + #[cfg(feature = "recursive_count")] + &recursive_count, + )?; let size_hint_method = gen_size_hint_method(&input)?; let name = input.ident; @@ -56,14 +61,22 @@ fn expand_derive_arbitrary(input: syn::DeriveInput) -> Result { // Build TypeGenerics and WhereClause without a lifetime let (_, ty_generics, where_clause) = generics.split_for_impl(); + #[cfg(feature = "recursive_count")] + let recursive_count_preamble = quote! { + extern crate std; + + ::std::thread_local! { + #[allow(non_upper_case_globals)] + static #recursive_count: ::core::cell::Cell = ::core::cell::Cell::new(0); + } + }; + + #[cfg(not(feature = "recursive_count"))] + let recursive_count_preamble = TokenStream::new(); + Ok(quote! { const _: () = { - extern crate std; - - ::std::thread_local! { - #[allow(non_upper_case_globals)] - static #recursive_count: ::core::cell::Cell = ::core::cell::Cell::new(0); - } + #recursive_count_preamble #[automatically_derived] impl #impl_generics arbitrary::Arbitrary<#lifetime_without_bounds> for #name #ty_generics #where_clause { @@ -150,9 +163,10 @@ fn add_trait_bounds(mut generics: Generics, lifetime: LifetimeParam) -> Generics } fn with_recursive_count_guard( - recursive_count: &syn::Ident, + #[cfg(feature = "recursive_count")] recursive_count: &syn::Ident, expr: impl quote::ToTokens, ) -> impl quote::ToTokens { + #[cfg(feature = "recursive_count")] quote! { let guard_against_recursion = u.is_empty(); if guard_against_recursion { @@ -175,25 +189,35 @@ fn with_recursive_count_guard( result } + + #[cfg(not(feature = "recursive_count"))] + quote! { (|| { #expr })() } } fn gen_arbitrary_method( input: &DeriveInput, lifetime: LifetimeParam, - recursive_count: &syn::Ident, + #[cfg(feature = "recursive_count")] recursive_count: &syn::Ident, ) -> Result { fn arbitrary_structlike( fields: &Fields, ident: &syn::Ident, lifetime: LifetimeParam, - recursive_count: &syn::Ident, + #[cfg(feature = "recursive_count")] recursive_count: &syn::Ident, ) -> Result { let arbitrary = construct(fields, |_idx, field| gen_constructor_for_field(field))?; - let body = with_recursive_count_guard(recursive_count, quote! { Ok(#ident #arbitrary) }); + let body = with_recursive_count_guard( + #[cfg(feature = "recursive_count")] + recursive_count, + quote! { Ok(#ident #arbitrary) }, + ); let arbitrary_take_rest = construct_take_rest(fields)?; - let take_rest_body = - with_recursive_count_guard(recursive_count, quote! { Ok(#ident #arbitrary_take_rest) }); + let take_rest_body = with_recursive_count_guard( + #[cfg(feature = "recursive_count")] + recursive_count, + quote! { Ok(#ident #arbitrary_take_rest) }, + ); Ok(quote! { fn arbitrary(u: &mut arbitrary::Unstructured<#lifetime>) -> arbitrary::Result { @@ -216,12 +240,13 @@ fn gen_arbitrary_method( } fn arbitrary_enum_method( - recursive_count: &syn::Ident, unstructured: TokenStream, variants: &[TokenStream], + #[cfg(feature = "recursive_count")] recursive_count: &syn::Ident, ) -> impl quote::ToTokens { let count = variants.len() as u64; with_recursive_count_guard( + #[cfg(feature = "recursive_count")] recursive_count, quote! { // Use a multiply + shift to generate a ranged random number @@ -239,7 +264,7 @@ fn gen_arbitrary_method( DataEnum { variants, .. }: &DataEnum, enum_name: &Ident, lifetime: LifetimeParam, - recursive_count: &syn::Ident, + #[cfg(feature = "recursive_count")] recursive_count: &syn::Ident, ) -> Result { let filtered_variants = variants.iter().filter(not_skipped); @@ -277,8 +302,18 @@ fn gen_arbitrary_method( (!variants.is_empty()) .then(|| { // TODO: Improve dealing with `u` vs. `&mut u`. - let arbitrary = arbitrary_enum_method(recursive_count, quote! { u }, &variants); - let arbitrary_take_rest = arbitrary_enum_method(recursive_count, quote! { &mut u }, &variants_take_rest); + let arbitrary = arbitrary_enum_method( + quote! { u }, + &variants, + #[cfg(feature = "recursive_count")] + recursive_count, + ); + let arbitrary_take_rest = arbitrary_enum_method( + quote! { &mut u }, + &variants_take_rest, + #[cfg(feature = "recursive_count")] + recursive_count, + ); quote! { fn arbitrary(u: &mut arbitrary::Unstructured<#lifetime>) -> arbitrary::Result { @@ -298,14 +333,27 @@ fn gen_arbitrary_method( let ident = &input.ident; match &input.data { - Data::Struct(data) => arbitrary_structlike(&data.fields, ident, lifetime, recursive_count), + Data::Struct(data) => arbitrary_structlike( + &data.fields, + ident, + lifetime, + #[cfg(feature = "recursive_count")] + recursive_count, + ), Data::Union(data) => arbitrary_structlike( &Fields::Named(data.fields.clone()), ident, lifetime, + #[cfg(feature = "recursive_count")] + recursive_count, + ), + Data::Enum(data) => arbitrary_enum( + data, + ident, + lifetime, + #[cfg(feature = "recursive_count")] recursive_count, ), - Data::Enum(data) => arbitrary_enum(data, ident, lifetime, recursive_count), } } diff --git a/src/tests.rs b/src/tests.rs index 0fc8984..9d998ab 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -2,12 +2,13 @@ use super::{Arbitrary, Result, Unstructured}; #[cfg(feature = "std")] use { + alloc::vec, core::{fmt::Debug, hash::Hash}, std::collections::HashSet, }; #[cfg(feature = "alloc")] -use alloc::{boxed::Box, rc::Rc, string::String, vec, vec::Vec}; +use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec}; #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] use alloc::sync::Arc; diff --git a/tests/derive.rs b/tests/derive.rs index c45cc24..5a378f9 100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -290,6 +290,7 @@ fn two_lifetimes() { assert_eq!(upper, None); } +#[cfg(feature = "recursive_count")] #[test] fn recursive_and_empty_input() { // None of the following derives should result in a stack overflow. See From 0bd3a381026bfe0dee9cec325463d656275746b3 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 8 Apr 2025 18:47:18 +1000 Subject: [PATCH 5/7] Make `recursive_count` default within the derive crate. --- Cargo.toml | 2 +- derive/Cargo.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2e9e1d9..518bc52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ documentation = "https://docs.rs/arbitrary/" rust-version = "1.63.0" # Keep in sync with version documented in the README.md [dependencies] -derive_arbitrary = { version = "~1.4.0", path = "./derive", optional = true } +derive_arbitrary = { version = "~1.4.0", path = "./derive", default-features = false, optional = true } [features] default = ["std", "recursive_count"] diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 8c31dda..1c07072 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -27,5 +27,6 @@ syn = { version = "2", features = ['derive', 'parsing', 'extra-traits'] } proc-macro = true [features] +default = ["recursive_count"] # Checks for unbounded recursion at runtime. Requires `std`. recursive_count = [] From f17a2ddac57a477ad9773de6516c242115cb274d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 8 Apr 2025 18:51:10 +1000 Subject: [PATCH 6/7] Include `derive` in `no_std` checked features --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index aa8c13c..956fb4e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -41,7 +41,7 @@ jobs: - run: rustup update - run: rustup toolchain add 1.82 - run: rustup target add x86_64-unknown-none --toolchain 1.82 - - run: cargo +1.82 check --no-default-features --features alloc,derive,core_error,core_net,alloc_ffi_cstring --target x86_64-unknown-none + - run: cargo +1.82 check --no-default-features --features alloc,derive,core_error,core_net,alloc_ffi_cstring,derive --target x86_64-unknown-none clippy: name: Clippy runs-on: ubuntu-latest From 52386c05ea5a6b2628a6a74931bb4a92fdf386a6 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Wed, 9 Apr 2025 20:53:54 +1000 Subject: [PATCH 7/7] Increase MSRV and remove redundant features --- .github/workflows/rust.yml | 10 +++++----- Cargo.toml | 8 +------- README.md | 2 +- derive/Cargo.toml | 2 +- src/error.rs | 4 ---- src/foreign/alloc/ffi/c_str.rs | 4 ---- src/foreign/alloc/ffi/mod.rs | 1 - src/foreign/core/mod.rs | 1 - src/foreign/core/net.rs | 4 ---- src/lib.rs | 4 ---- 10 files changed, 8 insertions(+), 32 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 956fb4e..9edd895 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -30,18 +30,18 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - run: rustup toolchain add 1.63 + - run: rustup toolchain add 1.81 - name: Build - run: cargo +1.63 check --lib --all-features + run: cargo +1.81 check --lib --all-features check_no_std: name: Check no_std runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: rustup update - - run: rustup toolchain add 1.82 - - run: rustup target add x86_64-unknown-none --toolchain 1.82 - - run: cargo +1.82 check --no-default-features --features alloc,derive,core_error,core_net,alloc_ffi_cstring,derive --target x86_64-unknown-none + - run: rustup toolchain add 1.81 + - run: rustup target add x86_64-unknown-none --toolchain 1.81 + - run: cargo +1.81 check --no-default-features --features alloc,derive --target x86_64-unknown-none clippy: name: Clippy runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 518bc52..7bd31cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ description = "The trait for generating structured data from unstructured data" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-fuzz/arbitrary/" documentation = "https://docs.rs/arbitrary/" -rust-version = "1.63.0" # Keep in sync with version documented in the README.md +rust-version = "1.81.0" # Keep in sync with version documented in the README.md [dependencies] derive_arbitrary = { version = "~1.4.0", path = "./derive", default-features = false, optional = true } @@ -30,12 +30,6 @@ derive = ["derive_arbitrary"] std = ["alloc"] # Enables support for the `alloc` crate. alloc = [] -# Enables using core::error::Error. Increases MSRV to at least 1.81.0 -core_error = [] -# Enables using core::net when `std` is disabled. Increases MSRV to at least 1.77.0 -core_net = [] -# Enables using `alloc::ffi::CString` when `std` is disabled. Increases MSRV to at least 1.64.0 -alloc_ffi_cstring = ["alloc"] # Checks for unbounded recursion at runtime. This does nothing without the "derive" feature. recursive_count = ["derive_arbitrary?/recursive_count"] diff --git a/README.md b/README.md index 5d75f31..a2a6c9c 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ impl<'a> Arbitrary<'a> for Rgb { -This crate is guaranteed to compile on stable Rust **1.63.0** and up. It might +This crate is guaranteed to compile on stable Rust **1.81.0** and up. It might compile with older versions but that may change in any new patch release. We reserve the right to increment the MSRV on minor releases, however we will diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 1c07072..5957903 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -16,7 +16,7 @@ description = "Derives arbitrary traits" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-fuzz/arbitrary" documentation = "https://docs.rs/arbitrary/" -rust-version = "1.63.0" +rust-version = "1.81.0" [dependencies] proc-macro2 = "1.0" diff --git a/src/error.rs b/src/error.rs index 07af97a..4cf49ae 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,10 +32,6 @@ impl fmt::Display for Error { } } -#[cfg(feature = "std")] -impl std::error::Error for Error {} - -#[cfg(all(not(feature = "std"), feature = "core_error"))] impl core::error::Error for Error {} /// A `Result` with the error type fixed as `arbitrary::Error`. diff --git a/src/foreign/alloc/ffi/c_str.rs b/src/foreign/alloc/ffi/c_str.rs index c704170..275d48f 100644 --- a/src/foreign/alloc/ffi/c_str.rs +++ b/src/foreign/alloc/ffi/c_str.rs @@ -1,7 +1,3 @@ -#[cfg(feature = "std")] -use std::ffi::CString; - -#[cfg(all(not(feature = "std"), feature = "alloc_ffi_cstring"))] use alloc::ffi::CString; use { diff --git a/src/foreign/alloc/ffi/mod.rs b/src/foreign/alloc/ffi/mod.rs index a170589..9238cfc 100644 --- a/src/foreign/alloc/ffi/mod.rs +++ b/src/foreign/alloc/ffi/mod.rs @@ -1,2 +1 @@ -#[cfg(any(feature = "std", feature = "alloc_ffi_cstring"))] mod c_str; diff --git a/src/foreign/core/mod.rs b/src/foreign/core/mod.rs index 17a819a..70cc717 100644 --- a/src/foreign/core/mod.rs +++ b/src/foreign/core/mod.rs @@ -9,7 +9,6 @@ mod char; mod cmp; mod iter; mod marker; -#[cfg(any(feature = "std", feature = "core_net"))] mod net; mod num; mod ops; diff --git a/src/foreign/core/net.rs b/src/foreign/core/net.rs index 9ea0210..014592d 100644 --- a/src/foreign/core/net.rs +++ b/src/foreign/core/net.rs @@ -1,7 +1,3 @@ -#[cfg(feature = "std")] -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; - -#[cfg(all(not(feature = "std"), feature = "core_net"))] use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use crate::{size_hint, Arbitrary, Result, Unstructured}; diff --git a/src/lib.rs b/src/lib.rs index d2518bd..ea155cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,10 +56,6 @@ impl core::fmt::Display for MaxRecursionReached { } } -#[cfg(feature = "std")] -impl std::error::Error for MaxRecursionReached {} - -#[cfg(all(not(feature = "std"), feature = "core_error"))] impl core::error::Error for MaxRecursionReached {} /// Generate arbitrary structured values from raw, unstructured data.