diff --git a/CHANGELOG.md b/CHANGELOG.md index e3ce5fd200..03fb6a751a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [v0.7.18] - 2026-04-15 ### Added @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- spsc: Fix integer overflow in iterators when N > usize::MAX/2 and the queue loops. +- spsc: Fix integer overflow leading to a panic in `len` when N == usize::MAX and debug assertions are enabled. + ## [v0.7.17] - 2023-12-04 ### Added @@ -525,6 +528,8 @@ architecture. - Initial release [Unreleased]: https://github.com/japaric/heapless/compare/v0.7.16...HEAD +[v0.7.18]: https://github.com/japaric/heapless/compare/v0.7.17...v0.7.18 +[v0.7.17]: https://github.com/japaric/heapless/compare/v0.7.16...v0.7.17 [v0.7.16]: https://github.com/japaric/heapless/compare/v0.7.15...v0.7.16 [v0.7.15]: https://github.com/japaric/heapless/compare/v0.7.14...v0.7.15 [v0.7.14]: https://github.com/japaric/heapless/compare/v0.7.13...v0.7.14 diff --git a/Cargo.toml b/Cargo.toml index 5758769bab..17c18bd51e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["static", "no-heap"] license = "MIT OR Apache-2.0" name = "heapless" repository = "https://github.com/japaric/heapless" -version = "0.7.17" +version = "0.7.18" [features] default = ["cas"] diff --git a/build.rs b/build.rs index 0bd68754bd..a7474a08e5 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,4 @@ -#![deny(warnings)] +//#![deny(warnings)] use std::{env, error::Error}; diff --git a/src/lib.rs b/src/lib.rs index cb53d5a515..1d34c6532f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ #![deny(missing_docs)] #![deny(rust_2018_compatibility)] #![deny(rust_2018_idioms)] -#![deny(warnings)] +// #![deny(warnings)] pub use binary_heap::BinaryHeap; pub use deque::Deque; diff --git a/src/spsc.rs b/src/spsc.rs index 3d00a4427d..11b38a3f97 100644 --- a/src/spsc.rs +++ b/src/spsc.rs @@ -110,7 +110,10 @@ impl Queue { #[inline] fn increment(val: usize) -> usize { - (val + 1) % N + // We know that N <= usize::MAX + // So this can only overflow if N == usize::MAX + // and in this case the overflow will be equivalent to the modulo N operation + val.wrapping_add(1) % N } /// Creates an empty queue with a fixed capacity of `N - 1` @@ -363,7 +366,11 @@ impl<'a, T, const N: usize> Iterator for Iter<'a, T, N> { if self.index < self.len { let head = self.rb.head.load(Ordering::Relaxed); - let i = (head + self.index) % N; + let i = match head.checked_add(self.index) { + Some(i) if i >= N => i - N, + Some(i) => i, + None => head.wrapping_add(self.index).wrapping_sub(N), + }; self.index += 1; Some(unsafe { &*(self.rb.buffer.get_unchecked(i).get() as *const T) }) @@ -380,7 +387,11 @@ impl<'a, T, const N: usize> Iterator for IterMut<'a, T, N> { if self.index < self.len { let head = self.rb.head.load(Ordering::Relaxed); - let i = (head + self.index) % N; + let i = match head.checked_add(self.index) { + Some(i) if i >= N => i - N, + Some(i) => i, + None => head.wrapping_add(self.index).wrapping_sub(N), + }; self.index += 1; Some(unsafe { &mut *(self.rb.buffer.get_unchecked(i).get() as *mut T) }) @@ -396,7 +407,11 @@ impl<'a, T, const N: usize> DoubleEndedIterator for Iter<'a, T, N> { let head = self.rb.head.load(Ordering::Relaxed); // self.len > 0, since it's larger than self.index > 0 - let i = (head + self.len - 1) % N; + let i = match head.checked_add(self.len - 1) { + Some(i) if i >= N => i - N, + Some(i) => i, + None => head.wrapping_add(self.len - 1).wrapping_sub(N), + }; self.len -= 1; Some(unsafe { &*(self.rb.buffer.get_unchecked(i).get() as *const T) }) } else { @@ -411,7 +426,11 @@ impl<'a, T, const N: usize> DoubleEndedIterator for IterMut<'a, T, N> { let head = self.rb.head.load(Ordering::Relaxed); // self.len > 0, since it's larger than self.index > 0 - let i = (head + self.len - 1) % N; + let i = match head.checked_add(self.len - 1) { + Some(i) if i >= N => i - N, + Some(i) => i, + None => head.wrapping_add(self.len - 1).wrapping_sub(N), + }; self.len -= 1; Some(unsafe { &mut *(self.rb.buffer.get_unchecked(i).get() as *mut T) }) } else { @@ -590,7 +609,10 @@ impl<'a, T, const N: usize> Producer<'a, T, N> { #[cfg(test)] mod tests { + use super::AtomicUsize; use crate::spsc::Queue; + use core::cell::UnsafeCell; + use core::mem::MaybeUninit; use hash32::Hasher; #[test] @@ -904,4 +926,74 @@ mod tests { }; assert_eq!(hash1, hash2); } + + // Test for some integer overflow bugs. See + // https://github.com/rust-embedded/heapless/pull/652#discussion_r3046630717 + // for more info + #[test] + #[cfg_attr(miri, ignore)] // too slow + fn test_len_overflow() { + let mut queue = Queue::<(), { usize::MAX }> { + head: AtomicUsize::new(usize::MAX), + tail: AtomicUsize::new(2), + buffer: [const { UnsafeCell::new(MaybeUninit::new(())) }; usize::MAX], + }; + queue.enqueue(()).unwrap(); + queue.enqueue(()).unwrap(); + + let collected: Vec<_> = queue.iter().collect(); + assert_eq!(&collected, &[&(); 4]); + } + + #[test] + #[cfg_attr(miri, ignore)] // too slow + fn test_usize_overflow_iter() { + let queue = Queue::<(), { usize::MAX - 1 }> { + head: AtomicUsize::new(usize::MAX - 3), + tail: AtomicUsize::new(2), + buffer: [const { UnsafeCell::new(MaybeUninit::new(())) }; usize::MAX - 1], + }; + + let collected: Vec<_> = queue.iter().collect(); + assert_eq!(&collected, &[&(); 4]); + } + + #[test] + #[cfg_attr(miri, ignore)] // too slow + fn test_usize_overflow_iter_mut() { + let mut queue = Queue::<(), { usize::MAX - 1 }> { + head: AtomicUsize::new(usize::MAX - 3), + tail: AtomicUsize::new(2), + buffer: [const { UnsafeCell::new(MaybeUninit::new(())) }; usize::MAX - 1], + }; + + let collected: Vec<_> = queue.iter_mut().collect(); + assert_eq!(&collected, &[&(); 4]); + } + + #[test] + #[cfg_attr(miri, ignore)] // too slow + fn test_usize_overflow_iter_rev() { + let queue = Queue::<(), { usize::MAX - 1 }> { + head: AtomicUsize::new(usize::MAX - 3), + tail: AtomicUsize::new(2), + buffer: [const { UnsafeCell::new(MaybeUninit::new(())) }; usize::MAX - 1], + }; + + let collected: Vec<_> = queue.iter().rev().collect(); + assert_eq!(&collected, &[&(); 4]); + } + + #[test] + #[cfg_attr(miri, ignore)] // too slow + fn test_usize_overflow_iter_mut_rev() { + let mut queue = Queue::<(), { usize::MAX - 1 }> { + head: AtomicUsize::new(usize::MAX - 3), + tail: AtomicUsize::new(2), + buffer: [const { UnsafeCell::new(MaybeUninit::new(())) }; usize::MAX - 1], + }; + + let collected: Vec<_> = queue.iter_mut().rev().collect(); + assert_eq!(&collected, &[&(); 4]); + } } diff --git a/tests/tsan.rs b/tests/tsan.rs index 946b2115d7..bf066b5c47 100644 --- a/tests/tsan.rs +++ b/tests/tsan.rs @@ -1,6 +1,6 @@ #![deny(rust_2018_compatibility)] #![deny(rust_2018_idioms)] -#![deny(warnings)] +//#![deny(warnings)] use std::thread;