From e732d1f273297da21126ee1f0b0060a513685557 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 7 Nov 2026 14:05:52 +0200 Subject: [PATCH 1/2] fix(small_string): utf8_index --- small_string/lib.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/small_string/lib.rs b/small_string/lib.rs index bc6674570..84cbfe88b 100644 --- a/small_string/lib.rs +++ b/small_string/lib.rs @@ -207,26 +207,35 @@ impl SmallString { pub fn utf8_index(&self, utf16_idx: usize) -> Option { if self.is_ascii() { return Some(utf16_idx); + } else if utf16_idx >= self.utf16_len() { + return None; } let mut current_utf16_index = 0; + let mut current_utf8_index = 0; let mut scratch = [0u16; 2]; - for (idx, ch) in self.as_wtf8().code_points().enumerate() { + for ch in self.as_wtf8().code_points() { match current_utf16_index.cmp(&utf16_idx) { - Ordering::Equal => return Some(idx), + Ordering::Equal => return Some(current_utf8_index), Ordering::Greater => return None, Ordering::Less => { current_utf16_index += ch .to_char() .map(|ch| ch.encode_utf16(&mut scratch).len()) - .unwrap_or(1) + .unwrap_or(1); + // Lone surrogates take 3 bytes in WTF-8. + current_utf8_index += match ch.to_u32() { + 0..=0x7F => 1, + 0x80..=0x7FF => 2, + 0x800..=0xFFFF => 3, + _ => 4, + }; } } } if current_utf16_index > utf16_idx { return None; } - debug_assert_eq!(utf16_idx, current_utf16_index); - Some(self.len()) + Some(current_utf8_index) } /// Get the corresponding WTF-16 code unit index for a given UTF-8 code @@ -615,3 +624,14 @@ fn str_conversion() { assert!(SmallString::try_from(too_large_unicode).is_err()); assert!(SmallString::try_from(Wtf8::from_str(too_large_unicode)).is_err()); } + +#[test] +fn utf8_indexing() { + let unicode = "あ<"; + let str = SmallString::try_from(unicode).unwrap(); + assert_eq!(str.len(), 4); + assert_eq!(str, unicode); + assert_eq!(str.utf8_index(0), Some(0)); + assert_eq!(str.utf8_index(1), Some(3)); + assert_eq!(str.utf8_index(2), None); +} From e6086f3733249ce538c5d2cdb6626eaf49b7135a Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 7 Nov 2026 14:14:40 +0200 Subject: [PATCH 2/2] fix(ecmascript): mapping of zero index in heap strings --- nova_vm/src/ecmascript/types/language/string/data.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nova_vm/src/ecmascript/types/language/string/data.rs b/nova_vm/src/ecmascript/types/language/string/data.rs index 2d7f5fea0..5f45119f0 100644 --- a/nova_vm/src/ecmascript/types/language/string/data.rs +++ b/nova_vm/src/ecmascript/types/language/string/data.rs @@ -163,8 +163,13 @@ impl StringRecord { // We got a None; that means we're looking at a latter // surrogate here. // SAFETY: idx is not 0. - let idx = mapping[unsafe { idx.unchecked_sub(1) }].unwrap(); - (idx.into(), true) + let idx = unsafe { idx.unchecked_sub(1) }; + let idx = if idx == 0 { + 0 + } else { + mapping[idx].unwrap().into() + }; + (idx, true) } } }