Skip to content

Commit d73ddfe

Browse files
committed
Use replace_range internally for most things
1 parent 3c1bd19 commit d73ddfe

File tree

6 files changed

+115
-128
lines changed

6 files changed

+115
-128
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,28 @@ fn main() -> Result<(), Error> {
7474
}
7575
```
7676

77+
# Miri
78+
79+
Tests can be run through Miri to ensure Undefined Behavior isn't triggered by them. It excludes diesel's integration `sqlite` tests as it's impossible to link to C libraries from Miri. And logs won't be persisted in doc tests as `env_logger` isn't supported by Miri either.
80+
81+
To run the tests with it do (requires nightly installed):
82+
83+
`cargo +nightly miri test --release --all-features`
84+
85+
# No Panic
86+
87+
There is a feature to enable the `no_panic` dependency, that will be enforced in every function. To be sure every panicking branch is removed. This depends on compiler optimizations and compilation may break on updates, or in different environments. We generally don't recommend using it, but it's useful in environments with high restrictions.
88+
89+
This feature will only be enforced in `release` builds (it checks for `not(debug_assertions)`)
90+
91+
But it's mostly used to test the library.
92+
93+
To run the tests with it do:
94+
95+
`cargo test --lib --tests --release --features=no-panic`
96+
97+
Both serde and diesel integrations can panic. Index trait implementations too.
98+
7799
## Licenses
78100

79101
[MIT](master/license/MIT) and [Apache-2.0](master/license/APACHE)

src/arraystring.rs

Lines changed: 53 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ where
335335
#[inline]
336336
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
337337
pub fn as_str(&self) -> &str {
338-
trace!("As str: {self}");
338+
trace!("As str");
339339
self.as_ref()
340340
}
341341

@@ -429,13 +429,9 @@ where
429429
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
430430
pub fn try_push_str(&mut self, string: impl AsRef<str>) -> Result<(), OutOfBounds> {
431431
trace!("Push str: {}", string.as_ref());
432-
let str = string.as_ref();
433-
if str.is_empty() {
434-
return Ok(());
435-
}
436-
is_inside_boundary(str.len() + self.len(), Self::capacity())?;
437-
unsafe { self.push_str_unchecked(str.as_bytes()) };
438-
Ok(())
432+
let len = self.len();
433+
self.replace_range(len..len, string)
434+
.map_err(|_| OutOfBounds)
439435
}
440436

441437
/// Pushes string slice to the end of the `ArrayString` truncating total size if bigger than [`capacity`].
@@ -460,23 +456,7 @@ where
460456
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
461457
pub fn push_str_truncate(&mut self, string: impl AsRef<str>) {
462458
trace!("Push str truncate: {}", string.as_ref());
463-
let str = string.as_ref().as_bytes();
464-
let size = Self::capacity().saturating_sub(self.len());
465-
if size == 0 || str.is_empty() {
466-
return;
467-
}
468-
let str = truncate_str(str, size);
469-
unsafe { self.push_str_unchecked(str) };
470-
}
471-
472-
#[inline]
473-
unsafe fn push_str_unchecked(&mut self, bytes: &[u8]) {
474-
core::ptr::copy_nonoverlapping(
475-
bytes.as_ptr(),
476-
self.array.as_mut_ptr().add(self.len()),
477-
bytes.len(),
478-
);
479-
self.size += bytes.len().into_lossy();
459+
let _ = self.try_push_str(truncate_str(string.as_ref(), Self::capacity() - self.len()));
480460
}
481461

482462
/// Inserts character to the end of the `ArrayString` erroring if total size if bigger than [`capacity`].
@@ -574,7 +554,7 @@ where
574554
#[inline]
575555
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
576556
pub fn trim(&mut self) {
577-
trace!("Trim");
557+
trace!("Trim: {self:?}");
578558
let mut start = self.len();
579559
for (pos, char) in self.as_str().char_indices() {
580560
if !char.is_whitespace() {
@@ -594,12 +574,9 @@ where
594574
end = pos;
595575
}
596576

597-
self.size = end.saturating_sub(start).into_lossy();
598-
599-
unsafe {
600-
let ptr = self.array.as_mut_ptr();
601-
core::ptr::copy(ptr.add(start), ptr, self.len());
602-
}
577+
let _ = self.replace_range(..start, "");
578+
// Note: Garanteed to not overflow but that should not be the bottleneck
579+
let _ = self.replace_range(end.saturating_sub(start).., "");
603580
}
604581

605582
/// Removes specified char from `ArrayString`
@@ -623,21 +600,23 @@ where
623600
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
624601
pub fn remove(&mut self, idx: usize) -> Result<char, Error> {
625602
debug!("Remove: {}", idx);
626-
is_inside_boundary(idx, self.len())?;
603+
is_inside_boundary(idx.saturating_add(1), self.len())?;
627604
is_char_boundary(self, idx)?;
628-
let char = unsafe {
629-
self.as_str()
630-
.get_unchecked(idx..)
631-
.chars()
632-
.next()
633-
.ok_or(OutOfBounds)?
634-
};
635-
let len = char.len_utf8();
636-
if idx + len < self.len() {
637-
self.array.copy_within(idx + len.., idx);
605+
606+
let mut end = if idx == self.len() { idx } else { idx + 1 };
607+
while !self.as_str().is_char_boundary(end) {
608+
end += 1;
638609
}
639-
self.size -= len.into_lossy();
640-
Ok(char)
610+
611+
let ch = self
612+
.as_str()
613+
.get(idx..end)
614+
.ok_or(Error::OutOfBounds)?
615+
.chars()
616+
.next()
617+
.ok_or(Error::Utf8)?;
618+
self.replace_range(idx..idx + ch.len_utf8(), "")?;
619+
Ok(ch)
641620
}
642621

643622
/// Retains only the characters specified by the predicate.
@@ -707,8 +686,7 @@ where
707686
/// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?;
708687
/// s.try_insert_str(1, "AB")?;
709688
/// s.try_insert_str(1, "BC")?;
710-
/// assert_eq!(s.try_insert_str(1, "0".repeat(ArrayString::<23>::capacity())),
711-
/// Err(Error::OutOfBounds));
689+
/// assert_eq!(s.try_insert_str(1, "0".repeat(ArrayString::<23>::capacity())), Err(Error::OutOfBounds));
712690
/// assert_eq!(s.as_str(), "ABCABBCD🤔");
713691
/// assert_eq!(s.try_insert_str(20, "C"), Err(Error::OutOfBounds));
714692
/// assert_eq!(s.try_insert_str(10, "D"), Err(Error::Utf8));
@@ -718,23 +696,9 @@ where
718696
#[inline]
719697
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
720698
pub fn try_insert_str(&mut self, idx: usize, string: impl AsRef<str>) -> Result<(), Error> {
721-
trace!("Try insert at {idx} str: {}", string.as_ref());
722-
let str = string.as_ref().as_bytes();
699+
trace!("Try insert at {idx} str: {:?} to {self:?}", string.as_ref());
723700
is_inside_boundary(idx, self.len())?;
724-
is_inside_boundary(idx + str.len() + self.len(), Self::capacity())?;
725-
is_char_boundary(self, idx)?;
726-
if str.is_empty() {
727-
return Ok(());
728-
}
729-
730-
unsafe {
731-
let ptr = self.array.as_mut_ptr().add(idx);
732-
core::ptr::copy(ptr, ptr.add(str.len()), self.len().saturating_sub(idx));
733-
core::ptr::copy(str.as_ptr(), ptr, str.len());
734-
}
735-
self.size = self.len().saturating_add(str.len()).into_lossy();
736-
737-
Ok(())
701+
self.replace_range(idx..idx, string)
738702
}
739703

740704
/// Inserts string slice at specified index, truncating size if bigger than [`capacity`].
@@ -770,24 +734,11 @@ where
770734
idx: usize,
771735
string: impl AsRef<str>,
772736
) -> Result<(), Error> {
773-
trace!("Insert str at {idx}: {}", string.as_ref());
774-
is_inside_boundary(idx, self.len())?;
775-
is_char_boundary(self, idx)?;
776-
let size = Self::capacity().saturating_sub(idx);
777-
if size == 0 {
778-
return Ok(());
779-
}
780-
let str = truncate_str(string.as_ref().as_bytes(), size);
781-
if str.is_empty() {
782-
return Ok(());
783-
}
784-
785-
unsafe {
786-
let ptr = self.array.as_mut_ptr().add(idx);
787-
core::ptr::copy(ptr, ptr.add(str.len()), self.len().saturating_sub(idx));
788-
core::ptr::copy(str.as_ptr(), ptr, str.len());
789-
}
790-
self.size = self.len().saturating_add(str.len()).into_lossy();
737+
trace!("Insert str at {idx}: {} to {self}", string.as_ref());
738+
self.try_insert_str(
739+
idx,
740+
truncate_str(string.as_ref(), Self::capacity() - self.len()),
741+
)?;
791742
Ok(())
792743
}
793744

@@ -855,13 +806,8 @@ where
855806
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
856807
pub fn split_off(&mut self, at: usize) -> Result<Self, Error> {
857808
debug!("Split off");
858-
is_inside_boundary(at, self.len())?;
859-
is_char_boundary(self, at)?;
860-
debug_assert!(at <= self.len() && self.as_str().is_char_boundary(at));
861-
let new =
862-
unsafe { Self::try_from_str(self.as_str().get_unchecked(at..)).unwrap_unchecked() };
863-
self.size = at.into_lossy();
864-
Ok(new)
809+
let drained = self.drain(at..)?.0;
810+
Ok(drained)
865811
}
866812

867813
/// Empties `ArrayString`
@@ -926,7 +872,7 @@ where
926872

927873
let drain = unsafe {
928874
let slice = self.as_str().get_unchecked(start..end);
929-
Self::try_from_str(slice).unwrap_unchecked()
875+
Self::try_from_str(slice)?
930876
};
931877
self.array.copy_within(end.., start);
932878
self.size = self
@@ -943,7 +889,7 @@ where
943889
/// # #[cfg(not(miri))] let _ = env_logger::try_init();
944890
/// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?;
945891
/// s.replace_range(2..4, "EFGHI")?;
946-
/// assert_eq!(s.as_bytes(), "ABEFGHI🤔".as_bytes());
892+
/// assert_eq!(s, "ABEFGHI🤔");
947893
///
948894
/// assert_eq!(s.replace_range(9.., "J"), Err(Error::Utf8));
949895
/// assert_eq!(s.replace_range(..90, "K"), Err(Error::OutOfBounds));
@@ -982,20 +928,30 @@ where
982928
}
983929
is_inside_boundary(start, end)?;
984930
is_inside_boundary(end, self.len())?;
931+
is_inside_boundary(str.len(), Self::capacity())?;
985932
is_char_boundary(self, start)?;
986933
is_char_boundary(self, end)?;
987-
is_inside_boundary(
988-
(start + str.len() + self.len()).saturating_sub(end),
989-
Self::capacity(),
990-
)?;
934+
// Will never overflow since start < end and str.len() cannot be bigger than 255
935+
is_inside_boundary(self.len() + str.len() + start - end, Self::capacity())?;
991936

992937
let this_len = self.len();
993938
unsafe {
994939
let ptr = self.array.as_mut_ptr();
995-
core::ptr::copy(ptr.add(end), ptr.add(str.len().saturating_add(start)), this_len.saturating_sub(end));
996-
core::ptr::copy(str.as_ptr(), ptr.add(start), str.len());
940+
core::ptr::copy(
941+
ptr.add(end),
942+
ptr.add(str.len().saturating_add(start)),
943+
this_len.saturating_sub(end),
944+
);
945+
if !str.is_empty() {
946+
core::ptr::copy(str.as_ptr(), ptr.add(start), str.len());
947+
}
997948
}
998-
self.size = self.len().saturating_add(str.len()).saturating_add(start).saturating_sub(end).into_lossy();
949+
self.size = self
950+
.len()
951+
.saturating_add(str.len())
952+
.saturating_add(start)
953+
.saturating_sub(end)
954+
.into_lossy();
999955
Ok(())
1000956
}
1001957
}

src/implementations.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ where
2828
#[inline]
2929
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
3030
fn as_ref(&self) -> &str {
31-
// Safety: our inner initialized slice should only contain valid utf-8
31+
// Safety: our byte slice should only contain valid utf-8
3232
// There is no way to invalidate the utf-8 of it from safe functions
3333
// And it's a invariant expected to be kept in unsafe functions
3434
debug_assert!(str::from_utf8(self.as_ref()).is_ok());
@@ -44,10 +44,10 @@ where
4444
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
4545
fn as_mut(&mut self) -> &mut str {
4646
let len = self.len();
47-
// Safety: size will always be between 0 and capacity, so get_unchecked_mut will never fail
47+
// Safety: len will always be between 0 and capacity, so get_unchecked_mut will never fail
4848
debug_assert!(len <= N);
4949
let slice = unsafe { self.array.as_mut_slice().get_unchecked_mut(..len) };
50-
// Safety: our inner initialized slice should only contain valid utf-8
50+
// Safety: our byte slice should only contain valid utf-8
5151
// There is no way to invalidate the utf-8 of it from safe functions
5252
// And it's a invariant expected to be kept in unsafe functions
5353
debug_assert!(str::from_utf8(slice).is_ok());
@@ -62,7 +62,7 @@ where
6262
#[inline]
6363
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
6464
fn as_ref(&self) -> &[u8] {
65-
// Safety: size will always be between 0 and capacity, so get_unchecked_mut will never fail
65+
// Safety: self.len() will always be between 0 and capacity, so get_unchecked_mut will never fail
6666
debug_assert!(self.len() <= N);
6767
unsafe { self.array.as_slice().get_unchecked(..self.len()) }
6868
}
@@ -117,6 +117,17 @@ where
117117
}
118118
}
119119

120+
impl<const N: usize> PartialEq<&str> for ArrayString<N>
121+
where
122+
Self: ValidCapacity,
123+
{
124+
#[inline]
125+
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
126+
fn eq(&self, other: &&str) -> bool {
127+
self.eq(*other)
128+
}
129+
}
130+
120131
impl<const N: usize> Borrow<str> for ArrayString<N>
121132
where
122133
Self: ValidCapacity,

src/integration.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ mod diesel_impl {
3131
#[cfg_attr(all(feature = "no-panic", not(debug_assertions)), no_panic)]
3232
fn from_sql(bytes: RawValue<'_, DB>) -> deserialize::Result<Self> {
3333
let ptr = <*const str as FromSql<ST, DB>>::from_sql(bytes)?;
34-
// Safety: We know that the pointer impl will never return null, it's just how diesel implements
34+
// Safety: We know that the pointer impl will never return null. We copied diesel's implementation for String
3535
debug_assert!(!ptr.is_null());
3636
Ok(Self::from_str_truncate(unsafe { &*ptr }))
3737
}
@@ -77,9 +77,9 @@ mod diesel_impl {
7777
#[cfg_attr(docs_rs_workaround, doc(cfg(feature = "serde-traits")))]
7878
#[cfg(feature = "serde-traits")]
7979
mod serde_impl {
80+
pub use crate::{arraystring::sealed::ValidCapacity, prelude::*};
8081
#[cfg(all(feature = "no-panic", not(debug_assertions)))]
8182
use no_panic::no_panic;
82-
pub use crate::{arraystring::sealed::ValidCapacity, prelude::*};
8383
pub use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize};
8484

8585
impl<const N: usize> Serialize for ArrayString<N>
@@ -180,7 +180,7 @@ mod tests {
180180
}
181181

182182
#[cfg(all(feature = "diesel-traits", feature = "std"))]
183-
use diesel::{dsl, insert_into, mysql, pg, prelude::*};
183+
use diesel::{dsl, mysql, pg, prelude::*};
184184

185185
#[cfg(all(feature = "diesel-traits", feature = "std"))]
186186
table! {
@@ -262,7 +262,7 @@ mod tests {
262262
}
263263

264264
#[test]
265-
#[cfg(all(feature = "diesel-traits", feature = "std"))]
265+
#[cfg(all(feature = "diesel-traits", feature = "std", not(miri)))]
266266
fn diesel_derive_query_sqlite() {
267267
let mut conn = diesel::sqlite::SqliteConnection::establish(":memory:").unwrap();
268268
let _ = diesel::sql_query("CREATE TABLE derives (id INTEGER, name VARCHAR(32));")
@@ -273,7 +273,7 @@ mod tests {
273273
name: ArrayString::try_from_str("Name1").unwrap(),
274274
};
275275

276-
let _ = insert_into(derives::table)
276+
let _ = diesel::insert_into(derives::table)
277277
.values(&string)
278278
.execute(&mut conn)
279279
.unwrap();
@@ -283,7 +283,7 @@ mod tests {
283283
}
284284

285285
#[test]
286-
#[cfg(all(feature = "diesel-traits", feature = "std"))]
286+
#[cfg(all(feature = "diesel-traits", feature = "std", not(miri)))]
287287
fn diesel_derive2_query_sqlite() {
288288
let mut conn = diesel::sqlite::SqliteConnection::establish(":memory:").unwrap();
289289
let _ = diesel::sql_query("CREATE TABLE derives (id INTEGER, name VARCHAR(32));")
@@ -294,7 +294,7 @@ mod tests {
294294
name: CacheString(ArrayString::try_from_str("Name1").unwrap()),
295295
};
296296

297-
let _ = insert_into(derives::table)
297+
let _ = diesel::insert_into(derives::table)
298298
.values(&string)
299299
.execute(&mut conn)
300300
.unwrap();

0 commit comments

Comments
 (0)