diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 4d1da0211..afda898ae 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -22,4 +22,5 @@ jobs: set -e (cd profiling && cargo bolero test collections::string_table::tests::fuzz_string_table -T 1min) (cd profiling && cargo bolero test collections::string_table::tests::fuzz_arena_allocator -T 1min) + (cd alloc && cargo bolero test chain::tests::fuzz -T 1min) diff --git a/alloc/src/chain.rs b/alloc/src/chain.rs index 656b6faee..4a87ddfbe 100644 --- a/alloc/src/chain.rs +++ b/alloc/src/chain.rs @@ -238,10 +238,7 @@ unsafe impl Allocator for ChainAllocator { debug_assert!(chain_node.remaining_capacity() >= layout.size()); - let result = chain_node.linear.allocate(layout); - // If this fails, there's a bug in the allocator. - debug_assert!(result.is_ok()); - result + chain_node.linear.allocate(layout) } unsafe fn deallocate(&self, _ptr: NonNull, _layout: Layout) { @@ -290,29 +287,59 @@ mod tests { use super::*; use allocator_api2::alloc::Global; + /// https://doc.rust-lang.org/beta/std/primitive.pointer.html#method.is_aligned_to + /// Convenience function until the std lib standardizes this. + fn is_aligned_to(p: *const T, align: usize) -> bool { + (p as *const u8 as usize) % align == 0 + } + #[test] fn fuzz() { bolero::check!() - .with_type::<(usize, usize, u32)>() - .for_each(|(size_hint, size, align_bits)| { + .with_type::<(usize, Vec<(usize, u32, usize, u8)>)>() + .for_each(|(size_hint, size_align_vec)| { + const MAX_SIZE: usize = 0x10000000; + // avoid SUMMARY: libFuzzer: out-of-memory + if *size_hint > MAX_SIZE { + return; + } let allocator = ChainAllocator::new_in(*size_hint, Global); - if *size == 0 {return}; - assert!(false); - // for now, only test 2**8 alignments - if *align_bits > 8 {return}; - let Some(align) = 2usize.checked_pow(*align_bits) else { - return; - }; - assert!(false); + for (size, align_bits, idx, val) in size_align_vec { + if *size == 0 || *size > MAX_SIZE { + return; + }; + if idx >= size { + return; + } - let Ok(layout) = Layout::from_size_align(*size, align) else { - return; - }; - let ptr = allocator.allocate(layout).unwrap(); - // deallocate doesn't return memory to the allocator, but it shouldn't - // panic, as that prevents its use in containers like Vec. - unsafe { allocator.deallocate(ptr.cast(), layout) }; + // Exit early if the alignment alone would be too big + if *align_bits > 32 { + return; + }; + + let align = 1usize << *align_bits; + let layout = Layout::from_size_align(*size, align).unwrap(); + + if layout.pad_to_align().size() > MAX_SIZE { + return; + }; + + if let Ok(mut ptr) = allocator.allocate(layout) { + assert!(is_aligned_to(ptr.as_ptr(), align)); + let obj = unsafe { ptr.as_mut() }; + // The object is guaranteed to be at least size, but can be larger + assert!(obj.len() >= *size); + + // Test that writing and reading a random index in the object works. + obj[*idx] = *val; + assert_eq!(obj[*idx], *val); + + // deallocate doesn't return memory to the allocator, but it shouldn't + // panic, as that prevents its use in containers like Vec. + unsafe { allocator.deallocate(ptr.cast(), layout) }; + } + } }) }