Skip to content

Commit 026b2ba

Browse files
authored
Avoid iterating from LenT::MAX down for truncation (#15)
1 parent 08e6257 commit 026b2ba

File tree

1 file changed

+66
-7
lines changed

1 file changed

+66
-7
lines changed

src/string.rs

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use alloc::{
22
borrow::{Cow, ToOwned},
33
boxed::Box,
4-
string::{String},
4+
string::String,
55
sync::Arc,
66
};
77
use core::{borrow::Borrow, hash::Hash, str::FromStr};
@@ -35,7 +35,7 @@ fn truncate_str(string: &str, max_len: usize) -> &str {
3535
}
3636
}
3737

38-
unreachable!("Len 0 is a char boundary")
38+
unreachable!("Len 0 is a char boundary");
3939
}
4040

4141
/// A fixed size String with length provided at creation denoted in [`ValidLength`], by default [`u32`].
@@ -61,10 +61,13 @@ impl<LenT: ValidLength> FixedString<LenT> {
6161
/// This method will not allocate, or copy the string data.
6262
///
6363
/// See [`Self::from_string_trunc`] for truncation behaviour.
64-
pub fn from_static_trunc(val: &'static str) -> Self {
65-
Self(FixedStringRepr::Static(StaticStr::from_static_str(
66-
truncate_str(val, LenT::MAX.to_usize()),
67-
)))
64+
pub fn from_static_trunc(mut val: &'static str) -> Self {
65+
let max_len = LenT::MAX.to_usize();
66+
if val.len() > max_len {
67+
val = truncate_str(val, max_len);
68+
}
69+
70+
Self(FixedStringRepr::Static(StaticStr::from_static_str(val)))
6871
}
6972

7073
/// Converts a `&str` into a [`FixedString`], allocating if the value cannot fit "inline".
@@ -93,7 +96,7 @@ impl<LenT: ValidLength> FixedString<LenT> {
9396
/// For lossless fallible conversion, convert to [`Box<str>`] using [`String::into_boxed_str`] and use [`TryFrom`].
9497
#[must_use]
9598
pub fn from_string_trunc(str: String) -> Self {
96-
match str.into_boxed_str().try_into() {
99+
match str.try_into() {
97100
Ok(val) => val,
98101
Err(err) => Self::from_string_trunc(truncate_string(err, LenT::MAX.to_usize())),
99102
}
@@ -438,6 +441,29 @@ mod test {
438441
}
439442
}
440443

444+
// primarily intended to ensure no hangs occur
445+
#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))]
446+
fn check_u32_partial_roundtrip_generic(to_fixed: fn(String) -> FixedString<u32>) {
447+
for i in 0..=400u32 {
448+
let original = "a".repeat(i as usize);
449+
let fixed = to_fixed(original);
450+
451+
assert!(fixed.bytes().all(|c| c == b'a'));
452+
assert_eq!(fixed.len(), i);
453+
454+
if !fixed.is_static() {
455+
assert_eq!(fixed.is_inline(), fixed.len() <= 12);
456+
}
457+
}
458+
}
459+
460+
fn check_default_generic<LenT: ValidLength>() {
461+
let fixed = FixedString::<LenT>::default();
462+
463+
assert!(fixed.is_static());
464+
assert_eq!(fixed.as_str(), "");
465+
}
466+
441467
#[test]
442468
fn test_truncating_behaviour() {
443469
const STR: &str = "______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________🦀";
@@ -482,6 +508,23 @@ mod test {
482508
});
483509
}
484510

511+
#[test]
512+
#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))]
513+
fn check_u32_partial_roundtrip() {
514+
check_u32_partial_roundtrip_generic(|original| {
515+
FixedString::<u32>::try_from(original).unwrap()
516+
});
517+
}
518+
519+
#[test]
520+
#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))]
521+
fn check_u32_partial_roundtrip_static() {
522+
check_u32_partial_roundtrip_generic(|original| {
523+
let static_str = Box::leak(original.into_boxed_str());
524+
FixedString::from_static_trunc(static_str)
525+
});
526+
}
527+
485528
#[test]
486529
#[cfg(feature = "serde")]
487530
fn check_u8_roundtrip_serde() {
@@ -504,6 +547,22 @@ mod test {
504547
});
505548
}
506549

550+
#[test]
551+
fn check_default_u8() {
552+
check_default_generic::<u8>();
553+
}
554+
555+
#[test]
556+
fn check_default_u16() {
557+
check_default_generic::<u16>();
558+
}
559+
560+
#[test]
561+
#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))]
562+
fn check_default_u32() {
563+
check_default_generic::<u32>();
564+
}
565+
507566
#[test]
508567
fn check_sizes() {
509568
type DoubleOpt<T> = Option<Option<T>>;

0 commit comments

Comments
 (0)