From 6b57afcad9539e3540afc0f9e108ea4252293afd Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Tue, 19 Jul 2022 17:51:09 -0400 Subject: [PATCH] truncate long slices instead of panicking --- crates/bevy_utils/src/label.rs | 45 ++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index 59133047e6c644..d3bc3d686d0ad5 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -66,23 +66,33 @@ pub struct StuffedStr<'a> { unsafe impl<'a> Send for StuffedStr<'a> {} unsafe impl<'a> Sync for StuffedStr<'a> {} +/// Truncates the passed string slice to be of length `len`, or shorter. +#[cold] +#[inline(never)] +fn truncate(str: &str, mut len: usize) -> &str { + // Just keep lowering the expected length until we hit a unicode boundary. + // This is messy but if the string is long enough that we need to truncate it, + // it's too long for anyone to notice what the end looks like. + loop { + if let Some(str) = str.get(..len) { + break str; + } + // In practice this won't be able to underflow, since `str.get(..0)` will always succeed. + len -= 1; + } +} + impl<'a> StuffedStr<'a> { const DATA_MASK: usize = !Self::LEN_MASK; const LEN_MASK: usize = !0 << 16; - pub fn new(str: &'a str, data: u16) -> Self { - #[cold] - #[inline(never)] - fn panic() -> ! { - // needs a better message but its late - panic!("string exceeds {} bytes", StuffedStr::LEN_MASK >> 16); - } - - let ptr = str.as_ptr(); + pub fn new(mut str: &'a str, data: u16) -> Self { // Make sure there's enough room to store the data. if str.len().leading_zeros() < 16 { - panic(); + str = truncate(str, Self::LEN_MASK >> 16); + debug_assert!(str.len().leading_zeros() < 16); } + let ptr = str.as_ptr(); let meta = data as usize | str.len() << 16; Self { ptr, @@ -202,3 +212,18 @@ macro_rules! define_label { } }; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_truncate() { + // Slice at byte '4' + assert_eq!(truncate("Hello, World!", 4), "Hell"); + + // Slicing off at byte '3' would be inside of the emoji, + // so instead the whole emoji gets sliced off. + assert_eq!(truncate("x😂", 3), "x"); + } +}