Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions lender/src/adapters/chunky.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,7 @@ where
} else {
// Exhaust
if self.len > 0 {
let skip = self
.len
.checked_mul(self.chunk_size)
.expect("overflow in Chunky::nth");
let skip = self.len.saturating_mul(self.chunk_size);
let _ = self.lender.advance_by(skip);
self.len = 0;
}
Expand Down Expand Up @@ -197,3 +194,36 @@ impl<L> FusedLender for Chunky<L> where L: FusedLender {}
// `next()` will actually produce. This violates the ExactSizeLender
// contract, which requires `len()` to match the actual remaining
// count. The chunk count is still available through `size_hint()`.

#[cfg(test)]
mod test {
use crate::prelude::*;

// A lender reporting len() == usize::MAX with O(1) advance_by, to exercise the
// exhaust branch's chunk-count * chunk_size multiplication near usize::MAX.
struct Huge;
impl<'l> crate::Lending<'l> for Huge {
type Lend = i32;
}
impl crate::Lender for Huge {
crate::check_covariance!();
fn next(&mut self) -> Option<crate::Lend<'_, Self>> {
Some(0)
}
fn advance_by(&mut self, _n: usize) -> Result<(), core::num::NonZeroUsize> {
Ok(())
}
}
impl crate::ExactSizeLender for Huge {
fn len(&self) -> usize {
usize::MAX
}
}

#[test]
fn test_nth_exhaust_no_overflow_panic() {
// len = usize::MAX.div_ceil(2); nth past the end must not panic on len * 2 overflow
let mut c = Huge.chunky(2);
assert!(c.nth(usize::MAX).is_none());
}
}
9 changes: 6 additions & 3 deletions lender/src/adapters/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ where

/// Returns the inner lender.
#[inline]
pub fn into_inner(self) -> L {
pub fn into_inner(mut self) -> L {
*self.inner.inner = None;
*AliasableBox::into_unique(self.inner.lender)
}
}
Expand Down Expand Up @@ -133,13 +134,15 @@ where

/// Returns the inner lender.
#[inline]
pub fn into_inner(self) -> L {
pub fn into_inner(mut self) -> L {
*self.inner.inner = None;
(*AliasableBox::into_unique(self.inner.lender)).into_inner()
}

/// Returns the inner lender and the mapping function.
#[inline]
pub fn into_parts(self) -> (L, Covar<F>) {
pub fn into_parts(mut self) -> (L, Covar<F>) {
*self.inner.inner = None;
(*AliasableBox::into_unique(self.inner.lender)).into_parts()
}
}
Expand Down
40 changes: 36 additions & 4 deletions lender/src/adapters/fuse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,11 @@ where
Self: Sized,
{
if !self.flag {
if let x @ Some(_) = self.lender.last() {
return x;
}
self.flag = true;
self.lender.last()
} else {
None
}
None
}

#[inline]
Expand Down Expand Up @@ -244,3 +243,36 @@ impl<L: Default + Lender> Default for Fuse<L> {
Fuse::new(L::default())
}
}

#[cfg(test)]
mod test {
use crate::prelude::*;

// A non-fused lender: yields 10, then None, then 30 if polled again.
struct Resurrect {
step: usize,
}
impl<'l> crate::Lending<'l> for Resurrect {
type Lend = i32;
}
impl crate::Lender for Resurrect {
crate::check_covariance!();
fn next(&mut self) -> Option<crate::Lend<'_, Self>> {
let s = self.step;
self.step += 1;
match s {
0 => Some(10),
2 => Some(30),
_ => None,
}
}
}

#[test]
fn test_last_fuses() {
let mut f = (Resurrect { step: 0 }).fuse();
assert_eq!(f.last(), Some(10)); // drives the inner lender to None
assert_eq!(f.next(), None); // must stay fused, not resurrect 30
assert_eq!(f.size_hint(), (0, Some(0)));
}
}
41 changes: 33 additions & 8 deletions lender/src/adapters/peekable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ where

/// Returns the inner lender.
#[inline]
pub fn into_inner(self) -> L {
pub fn into_inner(mut self) -> L {
*self.peeked = None;
*AliasableBox::into_unique(self.lender)
}

Expand Down Expand Up @@ -97,15 +98,25 @@ where
/// # use lender::prelude::*;
/// let mut lender = [1, 2, 3].iter().into_lender().peekable();
///
/// if let Some(p) = lender.peek_mut() {
/// // SAFETY: `p` is used and dropped within this scope; the lend never escapes the borrow.
/// if let Some(p) = unsafe { lender.peek_mut() } {
/// // p is &mut &i32, so we replace the reference
/// *p = &10;
/// }
/// assert_eq!(lender.next(), Some(&10));
/// assert_eq!(lender.next(), Some(&2));
/// ```
///
/// # Safety
///
/// The returned reference exposes the cached lend with the lender's full
/// storage lifetime. The caller must not, through the returned reference,
/// (a) move or copy out a lend that outlives this `&mut self` borrow, nor
/// (b) overwrite the referent with a lend borrowing data that does not
/// outlive this `Peekable`. Either lets the lend escape its borrow, causing
/// a use-after-free.
#[inline]
pub fn peek_mut(&mut self) -> Option<&'_ mut Lend<'this, L>> {
pub unsafe fn peek_mut(&mut self) -> Option<&'_ mut Lend<'this, L>> {
let lender = &mut self.lender;
self.peeked
.get_or_insert_with(|| {
Expand Down Expand Up @@ -232,12 +243,13 @@ where

#[inline]
fn count(mut self) -> usize {
let extra = match self.peeked.take() {
Some(None) => return 0,
Some(Some(_)) => 1,
None => 0,
};
let lender = *AliasableBox::into_unique(self.lender);
match self.peeked.take() {
Some(None) => 0,
Some(Some(_)) => 1 + lender.count(),
None => lender.count(),
}
extra + lender.count()
}

#[inline]
Expand Down Expand Up @@ -437,4 +449,17 @@ mod test {
"Peeked element pointer should point to the first element of the array"
);
}

#[test]
fn test_into_inner_and_count_after_peek() {
use crate::prelude::*;
let mut p = [1, 2, 3].iter().into_lender().peekable();
assert_eq!(p.peek(), Some(&&1));
assert_eq!(p.count(), 3); // peeked element counted, no panic/UB

let mut p = [1, 2, 3].iter().into_lender().peekable();
assert_eq!(p.peek(), Some(&&1));
let mut inner = p.into_inner(); // must not free-then-drop the cache
assert_eq!(inner.next(), Some(&2)); // peeked &1 consumed into the (now-dropped) cache
}
}
14 changes: 14 additions & 0 deletions lender/src/adapters/step_by.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ where
if n == 0 {
return self.lender.next();
}
let _ = self.lender.next();
n -= 1;
}
let mut step = self.step + 1;
Expand Down Expand Up @@ -248,3 +249,16 @@ where
impl<L> ExactSizeLender for StepBy<L> where L: ExactSizeLender {}

impl<L> FusedLender for StepBy<L> where L: FusedLender {}

#[cfg(test)]
mod test {
use crate::prelude::*;
#[test]
fn test_nth_fresh() {
// yields &0,&2,&4; nth(1) is the SECOND yielded element = &2 (was &1 before the fix)
let mut s = [0, 1, 2, 3, 4].iter().into_lender().step_by(2);
assert_eq!(s.nth(1), Some(&2));
// matches std::iter::StepBy
assert_eq!((0..5).step_by(2).nth(1), Some(2));
}
}
35 changes: 34 additions & 1 deletion lender/src/adapters/take.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ where
self.n -= n + 1;
self.lender.nth_back(m)
} else {
if len > 0 {
if self.n != 0 && len > 0 {
self.lender.nth_back(len - 1);
}
None
Expand Down Expand Up @@ -230,6 +230,9 @@ where

#[inline]
fn advance_back_by(&mut self, n: usize) -> Result<(), NonZeroUsize> {
if n == 0 {
return Ok(());
}
// Relies on ExactSizeLender: if len() is inaccurate,
// advanced_by_inner - trim_inner may underflow.
let trim_inner = self.lender.len().saturating_sub(self.n);
Expand All @@ -248,3 +251,33 @@ where
impl<L> ExactSizeLender for Take<L> where L: ExactSizeLender {}

impl<L> FusedLender for Take<L> where L: FusedLender {}

#[cfg(test)]
mod test {
use crate::prelude::*;

#[test]
fn test_advance_back_by_zero_is_noop() {
let mut t = [0, 1, 2, 3].iter().into_lender().take(2);
assert!(t.advance_back_by(0).is_ok());
// inner must be untouched: all four elements remain
let mut inner = t.into_inner();
assert_eq!(inner.next(), Some(&0));
assert_eq!(inner.next(), Some(&1));
assert_eq!(inner.next(), Some(&2));
assert_eq!(inner.next(), Some(&3));
assert_eq!(inner.next(), None);
}

#[test]
fn test_empty_take_nth_back_leaves_inner() {
let mut t = [0, 1, 2, 3].iter().into_lender().take(0);
assert_eq!(t.nth_back(0), None);
let mut inner = t.into_inner();
assert_eq!(inner.next(), Some(&0));
assert_eq!(inner.next(), Some(&1));
assert_eq!(inner.next(), Some(&2));
assert_eq!(inner.next(), Some(&3));
assert_eq!(inner.next(), None);
}
}
40 changes: 36 additions & 4 deletions lender/src/adapters/take_while.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,10 @@ where
#[inline]
fn next(&mut self) -> Option<Lend<'_, Self>> {
if !self.flag {
let x = self.lender.next()?;
if (self.predicate)(&x) {
return Some(x);
match self.lender.next() {
Some(x) if (self.predicate)(&x) => return Some(x),
_ => self.flag = true,
}
self.flag = true;
}
None
}
Expand Down Expand Up @@ -145,3 +144,36 @@ where
L: Lender,
{
}

#[cfg(test)]
mod test {
use crate::prelude::*;

// A non-fused lender: yields 10, then None, then 30 if polled again.
struct Resurrect {
step: usize,
}
impl<'l> crate::Lending<'l> for Resurrect {
type Lend = i32;
}
impl crate::Lender for Resurrect {
crate::check_covariance!();
fn next(&mut self) -> Option<crate::Lend<'_, Self>> {
let s = self.step;
self.step += 1;
match s {
0 => Some(10),
2 => Some(30),
_ => None,
}
}
}

#[test]
fn test_none_is_terminal() {
let mut tw = (Resurrect { step: 0 }).take_while(|_| true);
assert_eq!(tw.next(), Some(10));
assert_eq!(tw.next(), None); // inner yields None -> TakeWhile is done
assert_eq!(tw.next(), None); // must not resurrect 30 (TakeWhile is FusedLender)
}
}
44 changes: 40 additions & 4 deletions lender/src/fallible_adapters/chunky.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ where
} else {
// Exhaust
if self.len > 0 {
let skip = self
.len
.checked_mul(self.chunk_size)
.expect("overflow in Chunky::nth");
let skip = self.len.saturating_mul(self.chunk_size);
let _ = self.lender.advance_by(skip)?;
self.len = 0;
}
Expand Down Expand Up @@ -101,3 +98,42 @@ where
}

impl<L> FusedFallibleLender for Chunky<L> where L: FusedFallibleLender {}

#[cfg(test)]
mod test {
use core::convert::Infallible;

use crate::prelude::*;

// A fallible lender reporting len() == usize::MAX with O(1) advance_by.
struct Huge;
impl<'l> crate::FallibleLending<'l> for Huge {
type Lend = i32;
}
impl crate::FallibleLender for Huge {
type Error = Infallible;
crate::check_covariance_fallible!();
fn next(&mut self) -> Result<Option<crate::FallibleLend<'_, Self>>, Self::Error> {
Ok(Some(0))
}
fn advance_by(
&mut self,
_n: usize,
) -> Result<Result<(), core::num::NonZeroUsize>, Self::Error> {
Ok(Ok(()))
}
}
impl crate::ExactSizeFallibleLender for Huge {
fn len(&self) -> usize {
usize::MAX
}
}

#[test]
fn test_nth_exhaust_no_overflow_panic() -> Result<(), Infallible> {
// len = usize::MAX.div_ceil(2); nth past the end must not panic on len * 2 overflow
let mut c = Huge.chunky(2);
assert!(c.nth(usize::MAX)?.is_none());
Ok(())
}
}
Loading