diff --git a/src/ser.rs b/src/ser.rs index 9b14389c8..452b17072 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1534,23 +1534,6 @@ pub enum CharEscape { AsciiControl(u8), } -impl CharEscape { - #[inline] - fn from_escape_table(escape: u8, byte: u8) -> CharEscape { - match escape { - self::BB => CharEscape::Backspace, - self::TT => CharEscape::Tab, - self::NN => CharEscape::LineFeed, - self::FF => CharEscape::FormFeed, - self::RR => CharEscape::CarriageReturn, - self::QU => CharEscape::Quote, - self::BS => CharEscape::ReverseSolidus, - self::UU => CharEscape::AsciiControl(byte), - _ => unreachable!(), - } - } -} - /// This trait abstracts away serializing the JSON control characters, which allows the user to /// optionally pretty print the JSON output. pub trait Formatter { @@ -1784,30 +1767,33 @@ pub trait Formatter { { use self::CharEscape::*; - let s = match char_escape { - Quote => b"\\\"", - ReverseSolidus => b"\\\\", - Solidus => b"\\/", - Backspace => b"\\b", - FormFeed => b"\\f", - LineFeed => b"\\n", - CarriageReturn => b"\\r", - Tab => b"\\t", + let escape_char = match char_escape { + Quote => b'"', + ReverseSolidus => b'\\', + Solidus => b'/', + Backspace => b'b', + FormFeed => b'f', + LineFeed => b'n', + CarriageReturn => b'r', + Tab => b't', + AsciiControl(_) => b'u', + }; + + match char_escape { AsciiControl(byte) => { static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef"; let bytes = &[ b'\\', - b'u', + escape_char, b'0', b'0', HEX_DIGITS[(byte >> 4) as usize], HEX_DIGITS[(byte & 0xF) as usize], ]; - return writer.write_all(bytes); + writer.write_all(bytes) } - }; - - writer.write_all(s) + _ => writer.write_all(&[b'\\', escape_char]), + } } /// Writes the representation of a byte array. Formatters can choose whether @@ -2097,31 +2083,51 @@ where W: ?Sized + io::Write, F: ?Sized + Formatter, { - let bytes = value.as_bytes(); + let mut bytes = value.as_bytes(); - let mut start = 0; + let mut i = 0; + while i < bytes.len() { + let (string_run, rest) = bytes.split_at(i); + let (&byte, rest) = rest.split_first().unwrap(); - for (i, &byte) in bytes.iter().enumerate() { let escape = ESCAPE[byte as usize]; + + i += 1; if escape == 0 { continue; } - if start < i { - tri!(formatter.write_string_fragment(writer, &value[start..i])); + bytes = rest; + i = 0; + + // safety: string_run is a valid utf8 string, since we only split on ascii sequences + let string_run = unsafe { core::str::from_utf8_unchecked(string_run) }; + if !string_run.is_empty() { + tri!(formatter.write_string_fragment(writer, string_run)); } - let char_escape = CharEscape::from_escape_table(escape, byte); + let char_escape = match escape { + self::BB => CharEscape::Backspace, + self::TT => CharEscape::Tab, + self::NN => CharEscape::LineFeed, + self::FF => CharEscape::FormFeed, + self::RR => CharEscape::CarriageReturn, + self::QU => CharEscape::Quote, + self::BS => CharEscape::ReverseSolidus, + self::UU => CharEscape::AsciiControl(byte), + // safety: the escape table does not contain any other type of character. + _ => unsafe { core::hint::unreachable_unchecked() }, + }; tri!(formatter.write_char_escape(writer, char_escape)); - - start = i + 1; } - if start == bytes.len() { + // safety: bytes is a valid utf8 string, since we only split on ascii sequences + let string_run = unsafe { core::str::from_utf8_unchecked(bytes) }; + if string_run.is_empty() { return Ok(()); } - formatter.write_string_fragment(writer, &value[start..]) + formatter.write_string_fragment(writer, string_run) } const BB: u8 = b'b'; // \x08