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) } } } 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); +}