diff options
Diffstat (limited to 'src/util/eco.rs')
| -rw-r--r-- | src/util/eco.rs | 36 |
1 files changed, 27 insertions, 9 deletions
diff --git a/src/util/eco.rs b/src/util/eco.rs index 00f87872..2e326f32 100644 --- a/src/util/eco.rs +++ b/src/util/eco.rs @@ -2,11 +2,14 @@ use std::borrow::Borrow; use std::cmp::Ordering; +use std::convert::TryFrom; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::ops::{Add, AddAssign, Deref}; use std::rc::Rc; +use crate::diag::StrResult; + /// An economical string with inline storage and clone-on-write value semantics. #[derive(Clone)] pub struct EcoString(Repr); @@ -143,21 +146,25 @@ impl EcoString { } /// Repeat this string `n` times. - pub fn repeat(&self, n: usize) -> Self { + pub fn repeat(&self, n: i64) -> StrResult<Self> { + let (n, new) = usize::try_from(n) + .ok() + .and_then(|n| self.len().checked_mul(n).map(|new| (n, new))) + .ok_or_else(|| format!("cannot repeat this string {} times", n))?; + if let Repr::Small { buf, len } = &self.0 { let prev = usize::from(*len); - let new = prev.saturating_mul(n); if new <= LIMIT { let src = &buf[.. prev]; let mut buf = [0; LIMIT]; for i in 0 .. n { buf[prev * i .. prev * (i + 1)].copy_from_slice(src); } - return Self(Repr::Small { buf, len: new as u8 }); + return Ok(Self(Repr::Small { buf, len: new as u8 })); } } - self.as_str().repeat(n).into() + Ok(self.as_str().repeat(n).into()) } } @@ -216,6 +223,13 @@ impl Deref for EcoString { fn deref(&self) -> &str { match &self.0 { + // Safety: + // The buffer contents stem from correct UTF-8 sources: + // - Valid ASCII characters + // - Other string slices + // - Chars that were encoded with char::encode_utf8 + // Furthermore, we still do the bounds-check on the len in case + // it gets corrupted somehow. Repr::Small { buf, len } => unsafe { std::str::from_utf8_unchecked(&buf[.. usize::from(*len)]) }, @@ -424,13 +438,17 @@ mod tests { #[test] fn test_str_repeat() { // Test with empty string. - assert_eq!(EcoString::new().repeat(0), ""); - assert_eq!(EcoString::new().repeat(100), ""); + assert_eq!(EcoString::new().repeat(0).unwrap(), ""); + assert_eq!(EcoString::new().repeat(100).unwrap(), ""); // Test non-spilling and spilling case. let v = EcoString::from("abc"); - assert_eq!(v.repeat(0), ""); - assert_eq!(v.repeat(3), "abcabcabc"); - assert_eq!(v.repeat(5), "abcabcabcabcabc"); + assert_eq!(v.repeat(0).unwrap(), ""); + assert_eq!(v.repeat(3).unwrap(), "abcabcabc"); + assert_eq!(v.repeat(5).unwrap(), "abcabcabcabcabc"); + assert_eq!( + v.repeat(-1).unwrap_err(), + "cannot repeat this string -1 times", + ); } } |
