mirror of
https://github.com/nlohmann/json.git
synced 2026-11-01 10:25:43 +00:00
Binary readers and writers: faster UTF-8 checks of strings
With error_handler_t::strict (and replace/ignore), every string a binary reader reads is checked for valid UTF-8, and every string a binary writer writes. The readers used the byte-at-a-time decoder; the writers skipped ASCII eight bytes at a time. - New valid_utf8_string() decides strings of up to 16 ASCII bytes, most keys and many values, with one or two word tests, and checks longer strings 16 bytes at a time with the NEON/SSSE3 UTF-8 check the lexer uses (vector_utf8_valid_blocks: the same lookup, but quotes and control characters are ordinary characters). Other targets and JSON_NO_SIMD use valid_utf8_prefix() as before. - The readers check strings with it (for one-byte character types) in an out-of-line function, so that reading without a check does not pay for its code. - The writers' check_utf8() uses it, and the CBOR and MessagePack writers write a checked string of up to max_text_in_reservation characters in one reservation, as they write unchecked ones. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
7 files changed
+461
-64
No files matched your search
@@ -403,6 +403,7 @@ TEST_CASE("guard page")
|
||||
CHECK(nlohmann::detail::find_string_special(p, n) <= n);
|
||||
CHECK(nlohmann::detail::find_ascii_copyable_run(p, n) <= n);
|
||||
CHECK(nlohmann::detail::valid_utf8_prefix(p, n) <= n);
|
||||
CHECK(nlohmann::detail::valid_utf8_string(p, n) == (nlohmann::detail::valid_utf8_prefix(p, n) == n));
|
||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||
CHECK(nlohmann::detail::find_string_delimiter(p, n) <= n);
|
||||
#endif
|
||||
|
||||
@@ -13,6 +13,7 @@ using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "test_utils.hpp"
|
||||
@@ -1631,3 +1632,40 @@ TEST_CASE("Unicode RFC 3629")
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
|
||||
TEST_CASE("UTF-8 check of whole strings")
|
||||
{
|
||||
// valid_utf8_string() decides short strings with a word test and long ones
|
||||
// 16 bytes at a time where a vector check is available; it must agree with
|
||||
// the byte-wise check for every mix of ASCII, sequences of each length,
|
||||
// ill-formed and truncated sequences, at every length and offset
|
||||
const char* const pieces[] =
|
||||
{
|
||||
"a", "\x7F", "\xC3\xA9", "\xE2\x82\xAC", "\xE0\xA0\x80", "\xEF\xBF\xBF", "\xF0\x9F\x98\x80", "\xF4\x8F\xBF\xBF",
|
||||
"\x80", "\xC0\xAF", "\xC3", "\xE2\x82", "\xED\xA0\x80", "\xF0\x9F", "\xF4\x90\x80\x80", "\xFF"
|
||||
};
|
||||
// std::mt19937's sequence is fixed by the standard; no distribution is used
|
||||
std::mt19937 rng(5);
|
||||
std::size_t valid = 0;
|
||||
for (int i = 0; i < 100000; ++i)
|
||||
{
|
||||
std::string s;
|
||||
const std::size_t length = rng() % 100;
|
||||
const std::uint32_t ascii = rng() % 101;
|
||||
while (s.size() < length)
|
||||
{
|
||||
// mostly well-formed pieces (the first eight), so that long valid
|
||||
// strings are common
|
||||
s += (rng() % 100 < ascii) ? std::string("x") : std::string(pieces[rng() % (rng() % 8 == 0 ? 16 : 8)]);
|
||||
}
|
||||
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
|
||||
const bool expected = nlohmann::detail::is_valid_utf8(s);
|
||||
valid += expected ? 1 : 0;
|
||||
CAPTURE(s)
|
||||
CHECK(nlohmann::detail::valid_utf8_string(data, s.size()) == expected);
|
||||
CHECK((nlohmann::detail::valid_utf8_prefix(data, s.size()) == s.size()) == expected);
|
||||
}
|
||||
// both outcomes are common enough to be tested
|
||||
CHECK(valid > 20000);
|
||||
CHECK(valid < 80000);
|
||||
}
|
||||
Reference in new issue
Block a user