mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 11:17:13 +00:00
Escape characters below 0x60 from a table straight into the write buffer (#5822)
* Escape characters below 0x60 from a table straight into the write buffer When the bulk scanner in dump_escaped_impl() stops at a control character, a quote or a backslash, that byte used to go through the UTF-8 decoder and a switch that wrote the escape byte by byte into string_buffer, which was then copied into write_buffer at the next run. A byte below 0x60 at a character boundary is a complete code point, so it now skips the decoder: its escape sequence comes from a 96-entry table of 8-byte rows (sequence padded with zeros, length in the last byte), and all 8 bytes are copied straight into write_buffer with one room check. The check sits inside the existing character-boundary block, so it runs once per scanner stop and not once per byte; testing every byte cost about 6% on twitter.json with ensure_ascii. The switch in the decoder's accept branch lost its cases for characters below 0x60, which can no longer reach it. Output is byte-identical. The string-buffer boundary tests now use \u-escaped non-ASCII characters, since simple escapes no longer reach string_buffer, and a new test case checks every ASCII character at every position against a reference escaper. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Avoid raw string literals with backslashes in CHECK macros MSVC's traditional preprocessor mis-lexes raw string literals inside macro arguments and reports illegal escape sequences (C2017, C3850). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Sweep invalid UTF-8 at every position through dump() Compare dump() of strings with ill-formed UTF-8 (stray continuation bytes, invalid lead bytes, overlong encodings, surrogates, code points above U+10FFFF, and sequences cut short by ASCII, a quote, a backslash, a lead byte, or the end of the string) with an independent reference serializer, for every error handler and with ensure_ascii on and off. The pattern is put at every position of short strings to cross the strides of the bulk scanners, and of longer ones to cross the end of the 512-byte string buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
c7fd5ac19f
commit
dcb96e82fb
3 files changed
+884
-180
No files matched your search
Generated
+167
-78
@@ -28706,6 +28706,33 @@ class serializer
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// A byte below 0x60 is a complete code point and needs no
|
||||
// UTF-8 decoding. The scanner stops only at a control
|
||||
// character, a quote, or a backslash in that range, so look up
|
||||
// its escape sequence and write all 8 bytes of the table entry
|
||||
// straight into the write buffer; only the length stored in the
|
||||
// last byte of the entry is kept.
|
||||
if (data[i] < 0x60)
|
||||
{
|
||||
// emit any bytes still pending in string_buffer first to
|
||||
// preserve output order
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
const auto& escape = ascii_escapes()[data[i]];
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
std::memcpy(write_buffer.data() + write_buffer_pos, escape.data(), escape.size());
|
||||
write_buffer_pos += static_cast<std::size_t>(escape[7]);
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
||||
@@ -28714,92 +28741,39 @@ class serializer
|
||||
{
|
||||
case UTF8_ACCEPT: // decode found a new code point
|
||||
{
|
||||
switch (codepoint)
|
||||
// the code point is at least 0x60 (smaller ones are handled
|
||||
// above) and is not printable ASCII (which the scanner
|
||||
// copies): it is DEL (0x7F) under EnsureAscii or a non-ASCII
|
||||
// character
|
||||
if (EnsureAscii)
|
||||
{
|
||||
case 0x08: // backspace
|
||||
JSON_ASSERT(codepoint >= 0x7F);
|
||||
if (error_handler == error_handler_t::keep)
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'b';
|
||||
break;
|
||||
// this character was buffered as raw bytes below
|
||||
// in case it turned out to be part of an
|
||||
// ill-formed sequence (which is kept as is); now
|
||||
// that it decoded to a well-formed code point,
|
||||
// undo that and \u-escape it like any other
|
||||
// character instead
|
||||
bytes = bytes_after_last_accept;
|
||||
}
|
||||
|
||||
case 0x09: // horizontal tab
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 't';
|
||||
break;
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
|
||||
case 0x0A: // newline
|
||||
else
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'n';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0C: // formfeed
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'f';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0D: // carriage return
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'r';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x22: // quotation mark
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = '"';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x5C: // reverse solidus
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = '\\';
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// escape control characters (0x00..0x1F) or, if
|
||||
// EnsureAscii parameter is used, non-ASCII characters
|
||||
if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F)))
|
||||
{
|
||||
if (EnsureAscii && error_handler == error_handler_t::keep)
|
||||
{
|
||||
// this character was buffered as raw bytes
|
||||
// below in case it turned out to be part of
|
||||
// an ill-formed sequence (which is kept as
|
||||
// is); now that it decoded to a well-formed
|
||||
// code point, undo that and \u-escape it
|
||||
// like any other character instead
|
||||
bytes = bytes_after_last_accept;
|
||||
}
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy byte to buffer (all previous bytes
|
||||
// been copied have in default case above)
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
break;
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy byte to buffer (all previous bytes of the
|
||||
// sequence have been copied in the default case below)
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
@@ -29187,6 +29161,121 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the JSON form of each character below 0x60
|
||||
|
||||
Entry c holds the escape sequence of character c - the short form for
|
||||
\\b, \\t, \\n, \\f, \\r, \\" and \\\\, \\u00xx for the other control characters -
|
||||
or c itself if it needs no escaping, padded with zeros to 7 bytes. The last
|
||||
byte is the length of the sequence. The 8 bytes of an entry are copied at
|
||||
once, so writing an escape needs neither a branch nor a byte-wise loop.
|
||||
*/
|
||||
static const std::array<std::array<char, 8>, 0x60>& ascii_escapes() noexcept
|
||||
{
|
||||
static const std::array<std::array<char, 8>, 0x60> escapes =
|
||||
{
|
||||
{
|
||||
{{'\\', 'u', '0', '0', '0', '0', 0, 6}}, // 0x00
|
||||
{{'\\', 'u', '0', '0', '0', '1', 0, 6}}, // 0x01
|
||||
{{'\\', 'u', '0', '0', '0', '2', 0, 6}}, // 0x02
|
||||
{{'\\', 'u', '0', '0', '0', '3', 0, 6}}, // 0x03
|
||||
{{'\\', 'u', '0', '0', '0', '4', 0, 6}}, // 0x04
|
||||
{{'\\', 'u', '0', '0', '0', '5', 0, 6}}, // 0x05
|
||||
{{'\\', 'u', '0', '0', '0', '6', 0, 6}}, // 0x06
|
||||
{{'\\', 'u', '0', '0', '0', '7', 0, 6}}, // 0x07
|
||||
{{'\\', 'b', 0, 0, 0, 0, 0, 2}}, // 0x08
|
||||
{{'\\', 't', 0, 0, 0, 0, 0, 2}}, // 0x09
|
||||
{{'\\', 'n', 0, 0, 0, 0, 0, 2}}, // 0x0A
|
||||
{{'\\', 'u', '0', '0', '0', 'b', 0, 6}}, // 0x0B
|
||||
{{'\\', 'f', 0, 0, 0, 0, 0, 2}}, // 0x0C
|
||||
{{'\\', 'r', 0, 0, 0, 0, 0, 2}}, // 0x0D
|
||||
{{'\\', 'u', '0', '0', '0', 'e', 0, 6}}, // 0x0E
|
||||
{{'\\', 'u', '0', '0', '0', 'f', 0, 6}}, // 0x0F
|
||||
{{'\\', 'u', '0', '0', '1', '0', 0, 6}}, // 0x10
|
||||
{{'\\', 'u', '0', '0', '1', '1', 0, 6}}, // 0x11
|
||||
{{'\\', 'u', '0', '0', '1', '2', 0, 6}}, // 0x12
|
||||
{{'\\', 'u', '0', '0', '1', '3', 0, 6}}, // 0x13
|
||||
{{'\\', 'u', '0', '0', '1', '4', 0, 6}}, // 0x14
|
||||
{{'\\', 'u', '0', '0', '1', '5', 0, 6}}, // 0x15
|
||||
{{'\\', 'u', '0', '0', '1', '6', 0, 6}}, // 0x16
|
||||
{{'\\', 'u', '0', '0', '1', '7', 0, 6}}, // 0x17
|
||||
{{'\\', 'u', '0', '0', '1', '8', 0, 6}}, // 0x18
|
||||
{{'\\', 'u', '0', '0', '1', '9', 0, 6}}, // 0x19
|
||||
{{'\\', 'u', '0', '0', '1', 'a', 0, 6}}, // 0x1A
|
||||
{{'\\', 'u', '0', '0', '1', 'b', 0, 6}}, // 0x1B
|
||||
{{'\\', 'u', '0', '0', '1', 'c', 0, 6}}, // 0x1C
|
||||
{{'\\', 'u', '0', '0', '1', 'd', 0, 6}}, // 0x1D
|
||||
{{'\\', 'u', '0', '0', '1', 'e', 0, 6}}, // 0x1E
|
||||
{{'\\', 'u', '0', '0', '1', 'f', 0, 6}}, // 0x1F
|
||||
{{' ', 0, 0, 0, 0, 0, 0, 1}}, // 0x20
|
||||
{{'!', 0, 0, 0, 0, 0, 0, 1}}, // 0x21
|
||||
{{'\\', '"', 0, 0, 0, 0, 0, 2}}, // 0x22
|
||||
{{'#', 0, 0, 0, 0, 0, 0, 1}}, // 0x23
|
||||
{{'$', 0, 0, 0, 0, 0, 0, 1}}, // 0x24
|
||||
{{'%', 0, 0, 0, 0, 0, 0, 1}}, // 0x25
|
||||
{{'&', 0, 0, 0, 0, 0, 0, 1}}, // 0x26
|
||||
{{'\'', 0, 0, 0, 0, 0, 0, 1}}, // 0x27
|
||||
{{'(', 0, 0, 0, 0, 0, 0, 1}}, // 0x28
|
||||
{{')', 0, 0, 0, 0, 0, 0, 1}}, // 0x29
|
||||
{{'*', 0, 0, 0, 0, 0, 0, 1}}, // 0x2A
|
||||
{{'+', 0, 0, 0, 0, 0, 0, 1}}, // 0x2B
|
||||
{{',', 0, 0, 0, 0, 0, 0, 1}}, // 0x2C
|
||||
{{'-', 0, 0, 0, 0, 0, 0, 1}}, // 0x2D
|
||||
{{'.', 0, 0, 0, 0, 0, 0, 1}}, // 0x2E
|
||||
{{'/', 0, 0, 0, 0, 0, 0, 1}}, // 0x2F
|
||||
{{'0', 0, 0, 0, 0, 0, 0, 1}}, // 0x30
|
||||
{{'1', 0, 0, 0, 0, 0, 0, 1}}, // 0x31
|
||||
{{'2', 0, 0, 0, 0, 0, 0, 1}}, // 0x32
|
||||
{{'3', 0, 0, 0, 0, 0, 0, 1}}, // 0x33
|
||||
{{'4', 0, 0, 0, 0, 0, 0, 1}}, // 0x34
|
||||
{{'5', 0, 0, 0, 0, 0, 0, 1}}, // 0x35
|
||||
{{'6', 0, 0, 0, 0, 0, 0, 1}}, // 0x36
|
||||
{{'7', 0, 0, 0, 0, 0, 0, 1}}, // 0x37
|
||||
{{'8', 0, 0, 0, 0, 0, 0, 1}}, // 0x38
|
||||
{{'9', 0, 0, 0, 0, 0, 0, 1}}, // 0x39
|
||||
{{':', 0, 0, 0, 0, 0, 0, 1}}, // 0x3A
|
||||
{{';', 0, 0, 0, 0, 0, 0, 1}}, // 0x3B
|
||||
{{'<', 0, 0, 0, 0, 0, 0, 1}}, // 0x3C
|
||||
{{'=', 0, 0, 0, 0, 0, 0, 1}}, // 0x3D
|
||||
{{'>', 0, 0, 0, 0, 0, 0, 1}}, // 0x3E
|
||||
{{'?', 0, 0, 0, 0, 0, 0, 1}}, // 0x3F
|
||||
{{'@', 0, 0, 0, 0, 0, 0, 1}}, // 0x40
|
||||
{{'A', 0, 0, 0, 0, 0, 0, 1}}, // 0x41
|
||||
{{'B', 0, 0, 0, 0, 0, 0, 1}}, // 0x42
|
||||
{{'C', 0, 0, 0, 0, 0, 0, 1}}, // 0x43
|
||||
{{'D', 0, 0, 0, 0, 0, 0, 1}}, // 0x44
|
||||
{{'E', 0, 0, 0, 0, 0, 0, 1}}, // 0x45
|
||||
{{'F', 0, 0, 0, 0, 0, 0, 1}}, // 0x46
|
||||
{{'G', 0, 0, 0, 0, 0, 0, 1}}, // 0x47
|
||||
{{'H', 0, 0, 0, 0, 0, 0, 1}}, // 0x48
|
||||
{{'I', 0, 0, 0, 0, 0, 0, 1}}, // 0x49
|
||||
{{'J', 0, 0, 0, 0, 0, 0, 1}}, // 0x4A
|
||||
{{'K', 0, 0, 0, 0, 0, 0, 1}}, // 0x4B
|
||||
{{'L', 0, 0, 0, 0, 0, 0, 1}}, // 0x4C
|
||||
{{'M', 0, 0, 0, 0, 0, 0, 1}}, // 0x4D
|
||||
{{'N', 0, 0, 0, 0, 0, 0, 1}}, // 0x4E
|
||||
{{'O', 0, 0, 0, 0, 0, 0, 1}}, // 0x4F
|
||||
{{'P', 0, 0, 0, 0, 0, 0, 1}}, // 0x50
|
||||
{{'Q', 0, 0, 0, 0, 0, 0, 1}}, // 0x51
|
||||
{{'R', 0, 0, 0, 0, 0, 0, 1}}, // 0x52
|
||||
{{'S', 0, 0, 0, 0, 0, 0, 1}}, // 0x53
|
||||
{{'T', 0, 0, 0, 0, 0, 0, 1}}, // 0x54
|
||||
{{'U', 0, 0, 0, 0, 0, 0, 1}}, // 0x55
|
||||
{{'V', 0, 0, 0, 0, 0, 0, 1}}, // 0x56
|
||||
{{'W', 0, 0, 0, 0, 0, 0, 1}}, // 0x57
|
||||
{{'X', 0, 0, 0, 0, 0, 0, 1}}, // 0x58
|
||||
{{'Y', 0, 0, 0, 0, 0, 0, 1}}, // 0x59
|
||||
{{'Z', 0, 0, 0, 0, 0, 0, 1}}, // 0x5A
|
||||
{{'[', 0, 0, 0, 0, 0, 0, 1}}, // 0x5B
|
||||
{{'\\', '\\', 0, 0, 0, 0, 0, 2}}, // 0x5C
|
||||
{{']', 0, 0, 0, 0, 0, 0, 1}}, // 0x5D
|
||||
{{'^', 0, 0, 0, 0, 0, 0, 1}}, // 0x5E
|
||||
{{'_', 0, 0, 0, 0, 0, 0, 1}}, // 0x5F
|
||||
}
|
||||
};
|
||||
return escapes;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user