Compare commits

..
Author SHA1 Message Date
Niels Lohmann 9c349b1d7c Write integers of 17 to 20 digits eight digits at a time in dump() (#5964)
#5634 made dump() write integers below 10^16 eight digits at a time; larger
ones still took the two-digits-per-division loop and count_digits(). Random
64-bit integers almost all have 17 to 20 digits (99.9 % of signed_ints.json
and unsigned_ints.json), so Dump/signed_ints and Dump/unsigned_ints gained
only 1.4x over 3.12.0, against 2.3x for small integers.

A 64-bit magnitude is now written as its first one to four digits and two
groups of eight. Wider magnitudes (__int128 and class types, #5805) keep the
loop: number types of at most 64 bits are selected by type, so no comparison
is reported as always true; for others the value is compared. The sign
handling of #1447/#5815 (remove_sign() into number_magnitude_t) is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 13:54:45 +01:00
Niels Lohmann 367963f6bb Test escaped object keys decoded through NLOHMANN_DEFINE_TYPE from a json_view (#5972)
A json_view or json_document decodes a macro-defined struct directly and looks
the members up by their decoded keys. Only plain keys were covered so far.

The new section decodes structs defined with NLOHMANN_DEFINE_TYPE_INTRUSIVE,
..._WITH_DEFAULT and ..._WITH_NAMES from documents whose keys contain escape
sequences, and compares the result with the conversion of the materialized
value. It also pins the difference for a duplicate key written once plain and
once escaped (the view takes the first member, the materialized value keeps
the last one), and that an escaped backslash is part of the decoded key.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 13:54:20 +01:00
Niels Lohmann 4fe670b5f4 json_view: decode chains of unicode escapes in a loop (#5969)
After the first \uXXXX escape of a string, slow_string() now decodes the
escapes that follow it directly, while the next six bytes are \u and four hex
digits that are not a surrogate, instead of going round its main loop for each
of them (that is how Python's json.dumps writes non-ASCII text by default).
A surrogate, a bad digit, or fewer than six bytes leave the position at the
backslash: the main loop handles them as before and reports the same error at
the same position.

twitterescaped.json (simdjson-data, 549 KB, 33,036 escapes), json_document::parse(),
A/B in one binary, 6 code layouts, time after / before:

- Apple M1 Max, Apple clang 21: 0.897
- x86-64 Haswell, GCC 16.2: 0.907
- x86-64 Haswell, Clang 23: 0.910

The other files of the benchmark set (twitter, citm_catalog, canada, gsoc-2018,
apache_builds, instruments, tree-pretty, github_events, random, update-center,
semanticscholar-corpus, twitter_timeline) stay within the noise of each machine.

New test: chains of every length from 1 to 40 of escapes that give one, two,
and three bytes of UTF-8, followed by the end of the string, text, escapes,
surrogates, bad digits, and cut in every place, in keys and in values, against
json::parse().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 13:54:02 +01:00
9 changed files with 283 additions and 452 deletions

No files matched your search

@@ -13,15 +13,9 @@ function never throws an exception for invalid input, and the returned `#!cpp bo
is returned. is returned.
The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This
function runs the document's builder, which checks the text as [`parse()`](parse.md) does, while function runs the document's index builder (and discards the index), while `BasicJsonType::accept()` runs the SAX
`BasicJsonType::accept()` runs the SAX parser with a handler that ignores every event. Use this one if you go on to parser with a handler that ignores every event. Use this one if you go on to parse the same input with
parse the same input with [`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain [`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain yes/no answer.
yes/no answer.
When both `ignore_comments` and `ignore_trailing_commas` are `#!cpp false` (the default), the builder writes no index.
With either option set, it builds the index as [`parse()`](parse.md) does and discards it: the result is the same, only
the time differs. (Neither option needs the index; the variant that skips it exists only for the default options to
keep the code size down.)
## Template parameters ## Template parameters
@@ -1977,6 +1977,23 @@ class serializer
return; return;
} }
// 17 to 20 digits: the first one to four, then twice eight
if (magnitude_fits_64_bits(abs_value, std::integral_constant<bool, magnitude_has_at_most_64_bits>()))
{
const auto value = static_cast<std::uint64_t>(abs_value);
const std::uint64_t top = value / 10000000000000000u; // 1 to 1844
const std::uint64_t low16 = value - (top * 10000000000000000u);
const std::uint64_t middle = low16 / 100000000u;
const std::uint64_t first = dtoa_impl::eight_digit_bytes(top);
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
char* const p = buffer_ptr + n_chars;
dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u);
dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(middle) + 0x3030303030303030u);
dtoa_impl::store_msb_first(p + 16 - leading, dtoa_impl::eight_digit_bytes(low16 - (middle * 100000000u)) + 0x3030303030303030u);
write_buffer_pos += n_chars + 24 - leading;
return;
}
n_chars += count_digits(abs_value); n_chars += count_digits(abs_value);
// spare 1 byte for '\0' // spare 1 byte for '\0'
@@ -2122,6 +2139,23 @@ class serializer
} }
} }
/// whether every number_magnitude_t fits 64 bits
static constexpr bool magnitude_has_at_most_64_bits = std::numeric_limits<number_magnitude_t>::is_specialized
&& std::numeric_limits<number_magnitude_t>::digits <= 64;
/// whether @a x fits 64 bits: always if number_magnitude_t has at most
/// 64 bits (selected by type, as the comparison would be reported as
/// always true), else compared
static bool magnitude_fits_64_bits(const number_magnitude_t& /*unused*/, std::true_type /*unused*/) noexcept
{
return true;
}
static bool magnitude_fits_64_bits(const number_magnitude_t& x, std::false_type /*unused*/) noexcept
{
return x <= 18446744073709551615u;
}
/* /*
* Helper function for dump_integer * Helper function for dump_integer
* *
+25 -112
View File
@@ -69,8 +69,7 @@ struct parse_failure
}; };
/// FloatType: the number_float_t of the document, whose overflow parse() rejects /// FloatType: the number_float_t of the document, whose overflow parse() rejects
/// Store: false for accept(): the grammar, number, string and UTF-8 checks run as for parse(), but no node is written template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel>
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel, bool Store>
class builder class builder
{ {
public: public:
@@ -246,87 +245,11 @@ class builder
return p; return p;
} }
/// the string from p, the first backslash, control character, ill-formed UTF-8 byte, or the end, checked and not
/// decoded (accept() stores nothing, so the arena is not written): the checks, errors and error positions of
/// slow_string(), for everything up to the closing quote
NLOHMANN_VIEW_NOINLINE decoded check_string(const unsigned char* p)
{
const unsigned char* const end = m_end;
for (;;)
{
if (p == end)
{
return failed(error_code::string_missing_quote, p);
}
const unsigned char c = *p;
if (c == '"')
{
++p;
return decoded{p, 0, 0};
}
if (c != '\\')
{
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
}
++p;
if (p == end)
{
return failed(error_code::string_missing_quote, p);
}
const unsigned char d = *p++;
if (d == 'u')
{
const int cp = hex4(p); // (moves p past the four digits)
if (NLOHMANN_VIEW_UNLIKELY(cp < 0))
{
return failed(error_code::string_unicode_hex, p);
}
if (NLOHMANN_VIEW_UNLIKELY((cp & 0xF800) == 0xD800)) // a surrogate
{
if (cp >= 0xDC00)
{
return failed(error_code::string_surrogate_low, p);
}
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
{
return failed(error_code::string_surrogate_high, p);
}
p += 2;
const int lo = hex4(p); // (moves p past the four digits)
if (lo < 0)
{
return failed(error_code::string_unicode_hex, p);
}
if (lo < 0xDC00 || lo > 0xDFFF)
{
return failed(error_code::string_surrogate_high, p);
}
}
}
else if (!(d == '"' || d == '\\' || d == '/' || d == 'b' || d == 'f' || d == 'n' || d == 'r' || d == 't'))
{
--p;
return failed(error_code::string_escape, p);
}
if (p != end && *p == '\\')
{
continue; // (escapes in a row: no run of text between them)
}
p = scan_string_run(p, end); // the text up to the next backslash, quote, control character, or ill-formed UTF-8
}
}
/// s points to the start of the string, p to a backslash, a control /// s points to the start of the string, p to a backslash, a control
/// character, an ill-formed UTF-8 byte, or the end: decode the string into /// character, an ill-formed UTF-8 byte, or the end: decode the string into
/// the arena /// the arena
NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p) NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p)
{ {
if (!enabled(Store))
{
// (accept(): the string is checked, not decoded)
static_cast<void>(s);
return check_string(p);
}
// single-character escapes; 0: invalid (and 'u', handled separately) // single-character escapes; 0: invalid (and 'u', handled separately)
static const std::array<char, 128> simple_escape = static const std::array<char, 128> simple_escape =
{ {
@@ -420,6 +343,21 @@ class builder
} }
arena_ensure(w, w_end, 4); arena_ensure(w, w_end, 4);
w = put_utf8(w, cp); w = put_utf8(w, cp);
// A chain of unicode escapes ("\u3042\u3044\u3046", as Python's json.dumps writes non-ASCII text) is
// decoded here, without going round the loop above for each of them. Anything else (a surrogate, a
// bad digit, fewer than six bytes) leaves p at the backslash: the loop above handles it, and reports
// the same error at the same position as it would without this loop.
while (end - p >= 6 && p[0] == '\\' && p[1] == 'u')
{
const int next = hex_codepoint(p + 2);
if (next < 0 || (next & 0xF800) == 0xD800)
{
break;
}
p += 6;
arena_ensure(w, w_end, 4);
w = put_utf8(w, next);
}
} }
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{ {
@@ -686,14 +624,11 @@ run_found:
std::size_t max_depth = 0; std::size_t max_depth = 0;
NLOHMANN_VIEW_ALWAYS_INLINE bool run() NLOHMANN_VIEW_ALWAYS_INLINE bool run()
{
if (enabled(Store))
{ {
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b))); cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
base = cold.m_doc.nodes; base = cold.m_doc.nodes;
out = base; out = base;
cap = base + cold.m_doc.node_capacity; cap = base + cold.m_doc.node_capacity;
}
if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF) if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
{ {
@@ -876,12 +811,9 @@ close_object:
// a large object gets a hash index for its keys (objects only, so that closing // a large object gets a hash index for its keys (objects only, so that closing
// an array pays nothing for this) // an array pays nothing for this)
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members)) if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
{
if (enabled(Store))
{ {
cold.note_large_object(cur_idx); cold.note_large_object(cur_idx);
} }
}
close_container: close_container:
close(); close();
@@ -1040,12 +972,6 @@ indent_done:
/// an integer's value); two stores on little-endian targets /// an integer's value); two stores on little-endian targets
NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
{ {
if (!enabled(Store))
{
// accept(): nothing is stored, so what computes the arguments is not run either. base and out stay
// null: their difference, which open() takes, is 0.
return out;
}
if (NLOHMANN_VIEW_UNLIKELY(out == cap)) if (NLOHMANN_VIEW_UNLIKELY(out == cap))
{ {
const auto n = static_cast<std::size_t>(out - base); const auto n = static_cast<std::size_t>(out - base);
@@ -1103,13 +1029,10 @@ indent_done:
} }
NLOHMANN_VIEW_ALWAYS_INLINE void close() NLOHMANN_VIEW_ALWAYS_INLINE void close()
{
if (enabled(Store))
{ {
node& n = base[cur_idx]; node& n = base[cur_idx];
n.len = cur_count; n.len = cur_count;
n.next = static_cast<std::uint32_t>(out - base) - cur_idx; n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
}
if (--depth != 0) if (--depth != 0)
{ {
if (NLOHMANN_VIEW_LIKELY(depth <= 64)) if (NLOHMANN_VIEW_LIKELY(depth <= 64))
@@ -1284,44 +1207,34 @@ indent_done:
}; };
/// run the builder with compile-time options /// run the builder with compile-time options
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Store> template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd>
inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure) inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure)
{ {
if (sentinel) if (sentinel)
{ {
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true, Store> bld(d, src, size); builder<FloatType, Comments, TrailingCommas, NulIsEnd, true> bld(d, src, size);
const bool ok = bld.run(); const bool ok = bld.run();
failure = bld.failure(); failure = bld.failure();
return ok; return ok;
} }
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false, Store> bld(d, src, size); builder<FloatType, Comments, TrailingCommas, NulIsEnd, false> bld(d, src, size);
const bool ok = bld.run(); const bool ok = bld.run();
failure = bld.failure(); failure = bld.failure();
return ok; return ok;
} }
/// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the /// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the
/// number_float_t of the document; validate_only: accept(), only the result is /// number_float_t of the document
/// used
template<typename FloatType, bool NulIsEnd> template<typename FloatType, bool NulIsEnd>
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, bool validate_only, inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure)
parse_failure& failure)
{ {
if (validate_only && !comments && !trailing_commas)
{
// accept() of plain JSON: nothing is stored. The other option sets
// could skip the stores just as well; they keep the builder that
// stores only so that there are two more instantiations of the parse
// loop, not eight.
return build_with<FloatType, false, false, NulIsEnd, false>(d, src, size, sentinel, failure);
}
if (comments) if (comments)
{ {
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd, true>(d, src, size, sentinel, failure) return trailing_commas ? build_with<FloatType, true, true, NulIsEnd>(d, src, size, sentinel, failure)
: build_with<FloatType, true, false, NulIsEnd, true>(d, src, size, sentinel, failure); : build_with<FloatType, true, false, NulIsEnd>(d, src, size, sentinel, failure);
} }
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd, true>(d, src, size, sentinel, failure) return trailing_commas ? build_with<FloatType, false, true, NulIsEnd>(d, src, size, sentinel, failure)
: build_with<FloatType, false, false, NulIsEnd, true>(d, src, size, sentinel, failure); : build_with<FloatType, false, false, NulIsEnd>(d, src, size, sentinel, failure);
} }
} // namespace view } // namespace view
+19 -37
View File
@@ -1060,7 +1060,7 @@ class basic_json_document
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
basic_json_document d; basic_json_document d;
d.read_range(first, last, error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas); d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas);
return d; return d;
} }
@@ -1075,7 +1075,7 @@ class basic_json_document
basic_json_document d; basic_json_document d;
std::string buf; std::string buf;
append_input(buf, std::forward<InputType>(input)); append_input(buf, std::forward<InputType>(input));
d.build_owned(std::move(buf), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas); d.build_owned(std::move(buf), allow_exceptions, ignore_comments, ignore_trailing_commas);
return d; return d;
} }
@@ -1088,8 +1088,7 @@ class basic_json_document
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false) static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
{ {
basic_json_document d; basic_json_document d;
d.read_kind(std::forward<InputType>(input), on_error::validate, ignore_comments, ignore_trailing_commas, d.read(std::forward<InputType>(input), false, ignore_comments, ignore_trailing_commas);
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
return !d.is_discarded(); return !d.is_discarded();
} }
@@ -1105,7 +1104,7 @@ class basic_json_document
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
read_kind(std::forward<InputType>(input), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas, read_kind(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas,
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {}); std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
} }
@@ -1464,22 +1463,6 @@ class basic_json_document
return idx; return idx;
} }
/// what a failed parse does
enum class on_error : std::uint8_t
{
discard, ///< the document is discarded
raise, ///< parse_error is thrown
validate ///< accept(): the document is discarded, and no node is stored while parsing
};
static on_error error_mode(bool allow_exceptions) noexcept
{
return allow_exceptions ? on_error::raise : on_error::discard;
}
/// NulIsEnd of the builder: a null byte ends the input unless strict_nul_handling
static constexpr bool nul_is_end = !detail::abi_config::strict_nul_handling;
/// create the storage (sized for the input) on first use /// create the storage (sized for the input) on first use
void ensure_data(const char* src, std::size_t size) void ensure_data(const char* src, std::size_t size)
{ {
@@ -1491,7 +1474,7 @@ class basic_json_document
/// parse a buffer the document takes ownership of (it replaces the /// parse a buffer the document takes ownership of (it replaces the
/// previous owned copy) /// previous owned copy)
void build_owned(std::string&& buf, on_error allow_exceptions, bool comments, bool trailing_commas) void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas)
{ {
ensure_data(buf.data(), buf.size()); ensure_data(buf.data(), buf.size());
m_data->owned = std::move(buf); m_data->owned = std::move(buf);
@@ -1501,7 +1484,7 @@ class basic_json_document
/// parse a copy of the input; the copy is appended to the cleared owned /// parse a copy of the input; the copy is appended to the cleared owned
/// buffer, so that its capacity is reused /// buffer, so that its capacity is reused
template<typename T> template<typename T>
void build_copy(T&& input, on_error allow_exceptions, bool comments, bool trailing_commas) void build_copy(T&& input, bool allow_exceptions, bool comments, bool trailing_commas)
{ {
if (!m_data) if (!m_data)
{ {
@@ -1522,7 +1505,7 @@ class basic_json_document
} }
/// sentinel: src[size] is readable and 0 (std::string, C strings) /// sentinel: src[size] is readable and 0 (std::string, C strings)
void build(const char* src, std::size_t size, on_error allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel) void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
{ {
ensure_data(src, size); ensure_data(src, size);
document_data& d = *m_data; document_data& d = *m_data;
@@ -1549,8 +1532,7 @@ class basic_json_document
} }
else else
{ {
ok = detail::view::build<typename BasicJsonType::number_float_t, nul_is_end>(d, src, size, comments, trailing_commas, sentinel, ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure);
allow_exceptions == on_error::validate, failure);
} }
if (NLOHMANN_VIEW_LIKELY(ok)) if (NLOHMANN_VIEW_LIKELY(ok))
{ {
@@ -1563,7 +1545,7 @@ class basic_json_document
return; return;
} }
std::vector<std::uint32_t>().swap(d.large_objects); std::vector<std::uint32_t>().swap(d.large_objects);
if (allow_exceptions == on_error::raise) if (allow_exceptions)
{ {
detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas); detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas);
} }
@@ -1571,13 +1553,13 @@ class basic_json_document
// --- input dispatch (see detail::view::input_kind) --- // --- input dispatch (see detail::view::input_kind) ---
void read_kind(std::string&& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/) void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
{ {
build_owned(std::move(s), ae, c, tc); build_owned(std::move(s), ae, c, tc);
} }
template<typename CharT> template<typename CharT>
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/) void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
{ {
if (s == nullptr) if (s == nullptr)
{ {
@@ -1591,7 +1573,7 @@ class basic_json_document
} }
template<typename Array> template<typename Array>
void read_kind(Array& a, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/) void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
{ {
using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type; using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type;
const std::size_t n = std::extent<Array>::value; const std::size_t n = std::extent<Array>::value;
@@ -1602,7 +1584,7 @@ class basic_json_document
} }
template<typename T> template<typename T>
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/) void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
{ {
// std::basic_string guarantees data()[size()] == 0: use it as sentinel // std::basic_string guarantees data()[size()] == 0: use it as sentinel
const auto size = static_cast<std::size_t>(s.size()); const auto size = static_cast<std::size_t>(s.size());
@@ -1611,13 +1593,13 @@ class basic_json_document
} }
template<typename T> template<typename T>
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/) void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
{ {
build_copy(s, ae, c, tc); build_copy(s, ae, c, tc);
} }
template<typename CharT> template<typename CharT>
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/) void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
{ {
if (s == nullptr) if (s == nullptr)
{ {
@@ -1629,13 +1611,13 @@ class basic_json_document
} }
template<typename T> template<typename T>
void read_kind(T&& input, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/) void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
{ {
build_copy(std::forward<T>(input), ae, c, tc); build_copy(std::forward<T>(input), ae, c, tc);
} }
template<typename IteratorType> template<typename IteratorType>
void read_range(IteratorType first, IteratorType last, on_error ae, bool c, bool tc) void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc)
{ {
using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type; using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type;
// borrow the range where the library's input adapter scans it as one // borrow the range where the library's input adapter scans it as one
@@ -1646,14 +1628,14 @@ class basic_json_document
} }
template<typename IteratorType> template<typename IteratorType>
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::true_type /*contiguous bytes*/) void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
{ {
const auto size = static_cast<std::size_t>(std::distance(first, last)); const auto size = static_cast<std::size_t>(std::distance(first, last));
build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
} }
template<typename IteratorType> template<typename IteratorType>
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::false_type /*other*/) void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/)
{ {
build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc); build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc);
} }
+34
View File
@@ -36224,6 +36224,23 @@ class serializer
return; return;
} }
// 17 to 20 digits: the first one to four, then twice eight
if (magnitude_fits_64_bits(abs_value, std::integral_constant<bool, magnitude_has_at_most_64_bits>()))
{
const auto value = static_cast<std::uint64_t>(abs_value);
const std::uint64_t top = value / 10000000000000000u; // 1 to 1844
const std::uint64_t low16 = value - (top * 10000000000000000u);
const std::uint64_t middle = low16 / 100000000u;
const std::uint64_t first = dtoa_impl::eight_digit_bytes(top);
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
char* const p = buffer_ptr + n_chars;
dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u);
dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(middle) + 0x3030303030303030u);
dtoa_impl::store_msb_first(p + 16 - leading, dtoa_impl::eight_digit_bytes(low16 - (middle * 100000000u)) + 0x3030303030303030u);
write_buffer_pos += n_chars + 24 - leading;
return;
}
n_chars += count_digits(abs_value); n_chars += count_digits(abs_value);
// spare 1 byte for '\0' // spare 1 byte for '\0'
@@ -36369,6 +36386,23 @@ class serializer
} }
} }
/// whether every number_magnitude_t fits 64 bits
static constexpr bool magnitude_has_at_most_64_bits = std::numeric_limits<number_magnitude_t>::is_specialized
&& std::numeric_limits<number_magnitude_t>::digits <= 64;
/// whether @a x fits 64 bits: always if number_magnitude_t has at most
/// 64 bits (selected by type, as the comparison would be reported as
/// always true), else compared
static bool magnitude_fits_64_bits(const number_magnitude_t& /*unused*/, std::true_type /*unused*/) noexcept
{
return true;
}
static bool magnitude_fits_64_bits(const number_magnitude_t& x, std::false_type /*unused*/) noexcept
{
return x <= 18446744073709551615u;
}
/* /*
* Helper function for dump_integer * Helper function for dump_integer
* *
+44 -149
View File
@@ -1311,8 +1311,7 @@ struct parse_failure
}; };
/// FloatType: the number_float_t of the document, whose overflow parse() rejects /// FloatType: the number_float_t of the document, whose overflow parse() rejects
/// Store: false for accept(): the grammar, number, string and UTF-8 checks run as for parse(), but no node is written template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel>
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel, bool Store>
class builder class builder
{ {
public: public:
@@ -1488,87 +1487,11 @@ class builder
return p; return p;
} }
/// the string from p, the first backslash, control character, ill-formed UTF-8 byte, or the end, checked and not
/// decoded (accept() stores nothing, so the arena is not written): the checks, errors and error positions of
/// slow_string(), for everything up to the closing quote
NLOHMANN_VIEW_NOINLINE decoded check_string(const unsigned char* p)
{
const unsigned char* const end = m_end;
for (;;)
{
if (p == end)
{
return failed(error_code::string_missing_quote, p);
}
const unsigned char c = *p;
if (c == '"')
{
++p;
return decoded{p, 0, 0};
}
if (c != '\\')
{
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
}
++p;
if (p == end)
{
return failed(error_code::string_missing_quote, p);
}
const unsigned char d = *p++;
if (d == 'u')
{
const int cp = hex4(p); // (moves p past the four digits)
if (NLOHMANN_VIEW_UNLIKELY(cp < 0))
{
return failed(error_code::string_unicode_hex, p);
}
if (NLOHMANN_VIEW_UNLIKELY((cp & 0xF800) == 0xD800)) // a surrogate
{
if (cp >= 0xDC00)
{
return failed(error_code::string_surrogate_low, p);
}
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
{
return failed(error_code::string_surrogate_high, p);
}
p += 2;
const int lo = hex4(p); // (moves p past the four digits)
if (lo < 0)
{
return failed(error_code::string_unicode_hex, p);
}
if (lo < 0xDC00 || lo > 0xDFFF)
{
return failed(error_code::string_surrogate_high, p);
}
}
}
else if (!(d == '"' || d == '\\' || d == '/' || d == 'b' || d == 'f' || d == 'n' || d == 'r' || d == 't'))
{
--p;
return failed(error_code::string_escape, p);
}
if (p != end && *p == '\\')
{
continue; // (escapes in a row: no run of text between them)
}
p = scan_string_run(p, end); // the text up to the next backslash, quote, control character, or ill-formed UTF-8
}
}
/// s points to the start of the string, p to a backslash, a control /// s points to the start of the string, p to a backslash, a control
/// character, an ill-formed UTF-8 byte, or the end: decode the string into /// character, an ill-formed UTF-8 byte, or the end: decode the string into
/// the arena /// the arena
NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p) NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p)
{ {
if (!enabled(Store))
{
// (accept(): the string is checked, not decoded)
static_cast<void>(s);
return check_string(p);
}
// single-character escapes; 0: invalid (and 'u', handled separately) // single-character escapes; 0: invalid (and 'u', handled separately)
static const std::array<char, 128> simple_escape = static const std::array<char, 128> simple_escape =
{ {
@@ -1662,6 +1585,21 @@ class builder
} }
arena_ensure(w, w_end, 4); arena_ensure(w, w_end, 4);
w = put_utf8(w, cp); w = put_utf8(w, cp);
// A chain of unicode escapes ("\u3042\u3044\u3046", as Python's json.dumps writes non-ASCII text) is
// decoded here, without going round the loop above for each of them. Anything else (a surrogate, a
// bad digit, fewer than six bytes) leaves p at the backslash: the loop above handles it, and reports
// the same error at the same position as it would without this loop.
while (end - p >= 6 && p[0] == '\\' && p[1] == 'u')
{
const int next = hex_codepoint(p + 2);
if (next < 0 || (next & 0xF800) == 0xD800)
{
break;
}
p += 6;
arena_ensure(w, w_end, 4);
w = put_utf8(w, next);
}
} }
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{ {
@@ -1928,14 +1866,11 @@ run_found:
std::size_t max_depth = 0; std::size_t max_depth = 0;
NLOHMANN_VIEW_ALWAYS_INLINE bool run() NLOHMANN_VIEW_ALWAYS_INLINE bool run()
{
if (enabled(Store))
{ {
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b))); cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
base = cold.m_doc.nodes; base = cold.m_doc.nodes;
out = base; out = base;
cap = base + cold.m_doc.node_capacity; cap = base + cold.m_doc.node_capacity;
}
if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF) if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
{ {
@@ -2118,12 +2053,9 @@ close_object:
// a large object gets a hash index for its keys (objects only, so that closing // a large object gets a hash index for its keys (objects only, so that closing
// an array pays nothing for this) // an array pays nothing for this)
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members)) if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
{
if (enabled(Store))
{ {
cold.note_large_object(cur_idx); cold.note_large_object(cur_idx);
} }
}
close_container: close_container:
close(); close();
@@ -2282,12 +2214,6 @@ indent_done:
/// an integer's value); two stores on little-endian targets /// an integer's value); two stores on little-endian targets
NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
{ {
if (!enabled(Store))
{
// accept(): nothing is stored, so what computes the arguments is not run either. base and out stay
// null: their difference, which open() takes, is 0.
return out;
}
if (NLOHMANN_VIEW_UNLIKELY(out == cap)) if (NLOHMANN_VIEW_UNLIKELY(out == cap))
{ {
const auto n = static_cast<std::size_t>(out - base); const auto n = static_cast<std::size_t>(out - base);
@@ -2345,13 +2271,10 @@ indent_done:
} }
NLOHMANN_VIEW_ALWAYS_INLINE void close() NLOHMANN_VIEW_ALWAYS_INLINE void close()
{
if (enabled(Store))
{ {
node& n = base[cur_idx]; node& n = base[cur_idx];
n.len = cur_count; n.len = cur_count;
n.next = static_cast<std::uint32_t>(out - base) - cur_idx; n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
}
if (--depth != 0) if (--depth != 0)
{ {
if (NLOHMANN_VIEW_LIKELY(depth <= 64)) if (NLOHMANN_VIEW_LIKELY(depth <= 64))
@@ -2526,44 +2449,34 @@ indent_done:
}; };
/// run the builder with compile-time options /// run the builder with compile-time options
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Store> template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd>
inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure) inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure)
{ {
if (sentinel) if (sentinel)
{ {
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true, Store> bld(d, src, size); builder<FloatType, Comments, TrailingCommas, NulIsEnd, true> bld(d, src, size);
const bool ok = bld.run(); const bool ok = bld.run();
failure = bld.failure(); failure = bld.failure();
return ok; return ok;
} }
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false, Store> bld(d, src, size); builder<FloatType, Comments, TrailingCommas, NulIsEnd, false> bld(d, src, size);
const bool ok = bld.run(); const bool ok = bld.run();
failure = bld.failure(); failure = bld.failure();
return ok; return ok;
} }
/// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the /// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the
/// number_float_t of the document; validate_only: accept(), only the result is /// number_float_t of the document
/// used
template<typename FloatType, bool NulIsEnd> template<typename FloatType, bool NulIsEnd>
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, bool validate_only, inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure)
parse_failure& failure)
{ {
if (validate_only && !comments && !trailing_commas)
{
// accept() of plain JSON: nothing is stored. The other option sets
// could skip the stores just as well; they keep the builder that
// stores only so that there are two more instantiations of the parse
// loop, not eight.
return build_with<FloatType, false, false, NulIsEnd, false>(d, src, size, sentinel, failure);
}
if (comments) if (comments)
{ {
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd, true>(d, src, size, sentinel, failure) return trailing_commas ? build_with<FloatType, true, true, NulIsEnd>(d, src, size, sentinel, failure)
: build_with<FloatType, true, false, NulIsEnd, true>(d, src, size, sentinel, failure); : build_with<FloatType, true, false, NulIsEnd>(d, src, size, sentinel, failure);
} }
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd, true>(d, src, size, sentinel, failure) return trailing_commas ? build_with<FloatType, false, true, NulIsEnd>(d, src, size, sentinel, failure)
: build_with<FloatType, false, false, NulIsEnd, true>(d, src, size, sentinel, failure); : build_with<FloatType, false, false, NulIsEnd>(d, src, size, sentinel, failure);
} }
} // namespace view } // namespace view
@@ -10203,7 +10116,7 @@ class basic_json_document
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
basic_json_document d; basic_json_document d;
d.read_range(first, last, error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas); d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas);
return d; return d;
} }
@@ -10218,7 +10131,7 @@ class basic_json_document
basic_json_document d; basic_json_document d;
std::string buf; std::string buf;
append_input(buf, std::forward<InputType>(input)); append_input(buf, std::forward<InputType>(input));
d.build_owned(std::move(buf), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas); d.build_owned(std::move(buf), allow_exceptions, ignore_comments, ignore_trailing_commas);
return d; return d;
} }
@@ -10231,8 +10144,7 @@ class basic_json_document
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false) static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
{ {
basic_json_document d; basic_json_document d;
d.read_kind(std::forward<InputType>(input), on_error::validate, ignore_comments, ignore_trailing_commas, d.read(std::forward<InputType>(input), false, ignore_comments, ignore_trailing_commas);
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
return !d.is_discarded(); return !d.is_discarded();
} }
@@ -10248,7 +10160,7 @@ class basic_json_document
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
read_kind(std::forward<InputType>(input), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas, read_kind(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas,
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {}); std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
} }
@@ -10607,22 +10519,6 @@ class basic_json_document
return idx; return idx;
} }
/// what a failed parse does
enum class on_error : std::uint8_t
{
discard, ///< the document is discarded
raise, ///< parse_error is thrown
validate ///< accept(): the document is discarded, and no node is stored while parsing
};
static on_error error_mode(bool allow_exceptions) noexcept
{
return allow_exceptions ? on_error::raise : on_error::discard;
}
/// NulIsEnd of the builder: a null byte ends the input unless strict_nul_handling
static constexpr bool nul_is_end = !detail::abi_config::strict_nul_handling;
/// create the storage (sized for the input) on first use /// create the storage (sized for the input) on first use
void ensure_data(const char* src, std::size_t size) void ensure_data(const char* src, std::size_t size)
{ {
@@ -10634,7 +10530,7 @@ class basic_json_document
/// parse a buffer the document takes ownership of (it replaces the /// parse a buffer the document takes ownership of (it replaces the
/// previous owned copy) /// previous owned copy)
void build_owned(std::string&& buf, on_error allow_exceptions, bool comments, bool trailing_commas) void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas)
{ {
ensure_data(buf.data(), buf.size()); ensure_data(buf.data(), buf.size());
m_data->owned = std::move(buf); m_data->owned = std::move(buf);
@@ -10644,7 +10540,7 @@ class basic_json_document
/// parse a copy of the input; the copy is appended to the cleared owned /// parse a copy of the input; the copy is appended to the cleared owned
/// buffer, so that its capacity is reused /// buffer, so that its capacity is reused
template<typename T> template<typename T>
void build_copy(T&& input, on_error allow_exceptions, bool comments, bool trailing_commas) void build_copy(T&& input, bool allow_exceptions, bool comments, bool trailing_commas)
{ {
if (!m_data) if (!m_data)
{ {
@@ -10665,7 +10561,7 @@ class basic_json_document
} }
/// sentinel: src[size] is readable and 0 (std::string, C strings) /// sentinel: src[size] is readable and 0 (std::string, C strings)
void build(const char* src, std::size_t size, on_error allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel) void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
{ {
ensure_data(src, size); ensure_data(src, size);
document_data& d = *m_data; document_data& d = *m_data;
@@ -10692,8 +10588,7 @@ class basic_json_document
} }
else else
{ {
ok = detail::view::build<typename BasicJsonType::number_float_t, nul_is_end>(d, src, size, comments, trailing_commas, sentinel, ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure);
allow_exceptions == on_error::validate, failure);
} }
if (NLOHMANN_VIEW_LIKELY(ok)) if (NLOHMANN_VIEW_LIKELY(ok))
{ {
@@ -10706,7 +10601,7 @@ class basic_json_document
return; return;
} }
std::vector<std::uint32_t>().swap(d.large_objects); std::vector<std::uint32_t>().swap(d.large_objects);
if (allow_exceptions == on_error::raise) if (allow_exceptions)
{ {
detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas); detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas);
} }
@@ -10714,13 +10609,13 @@ class basic_json_document
// --- input dispatch (see detail::view::input_kind) --- // --- input dispatch (see detail::view::input_kind) ---
void read_kind(std::string&& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/) void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
{ {
build_owned(std::move(s), ae, c, tc); build_owned(std::move(s), ae, c, tc);
} }
template<typename CharT> template<typename CharT>
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/) void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
{ {
if (s == nullptr) if (s == nullptr)
{ {
@@ -10734,7 +10629,7 @@ class basic_json_document
} }
template<typename Array> template<typename Array>
void read_kind(Array& a, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/) void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
{ {
using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type; using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type;
const std::size_t n = std::extent<Array>::value; const std::size_t n = std::extent<Array>::value;
@@ -10745,7 +10640,7 @@ class basic_json_document
} }
template<typename T> template<typename T>
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/) void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
{ {
// std::basic_string guarantees data()[size()] == 0: use it as sentinel // std::basic_string guarantees data()[size()] == 0: use it as sentinel
const auto size = static_cast<std::size_t>(s.size()); const auto size = static_cast<std::size_t>(s.size());
@@ -10754,13 +10649,13 @@ class basic_json_document
} }
template<typename T> template<typename T>
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/) void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
{ {
build_copy(s, ae, c, tc); build_copy(s, ae, c, tc);
} }
template<typename CharT> template<typename CharT>
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/) void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
{ {
if (s == nullptr) if (s == nullptr)
{ {
@@ -10772,13 +10667,13 @@ class basic_json_document
} }
template<typename T> template<typename T>
void read_kind(T&& input, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/) void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
{ {
build_copy(std::forward<T>(input), ae, c, tc); build_copy(std::forward<T>(input), ae, c, tc);
} }
template<typename IteratorType> template<typename IteratorType>
void read_range(IteratorType first, IteratorType last, on_error ae, bool c, bool tc) void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc)
{ {
using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type; using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type;
// borrow the range where the library's input adapter scans it as one // borrow the range where the library's input adapter scans it as one
@@ -10789,14 +10684,14 @@ class basic_json_document
} }
template<typename IteratorType> template<typename IteratorType>
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::true_type /*contiguous bytes*/) void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
{ {
const auto size = static_cast<std::size_t>(std::distance(first, last)); const auto size = static_cast<std::size_t>(std::distance(first, last));
build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
} }
template<typename IteratorType> template<typename IteratorType>
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::false_type /*other*/) void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/)
{ {
build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc); build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc);
} }
+54 -145
View File
@@ -59,8 +59,7 @@ built build(const std::string& text, bool comments, bool trailing_commas, bool s
r.copy.assign(text.begin(), text.end()); r.copy.assign(text.begin(), text.end());
src = r.copy.data(); src = r.copy.data();
} }
constexpr bool nul_is_end = !nlohmann::detail::abi_config::strict_nul_handling; r.ok = nlohmann::detail::view::build < FloatType, !nlohmann::detail::abi_config::strict_nul_handling > (*r.data, src, text.size(), comments, trailing_commas, sentinel, r.failure);
r.ok = nlohmann::detail::view::build<FloatType, nul_is_end>(*r.data, src, text.size(), comments, trailing_commas, sentinel, false, r.failure);
r.data->src = src; r.data->src = src;
r.data->base[0] = src; r.data->base[0] = src;
r.data->base[1] = r.data->arena.data(); r.data->base[1] = r.data->arena.data();
@@ -173,22 +172,6 @@ void check_string(const std::string& content)
} }
} }
// accept() stores no nodes: it must accept and reject what json::accept does, for all options and for inputs with
// and without a terminating NUL
void check_accept(const std::string& text)
{
CAPTURE(text)
const std::vector<char> bytes(text.begin(), text.end());
for (int options = 0; options < 4; ++options)
{
const bool comments = (options & 1) != 0;
const bool trailing_commas = (options & 2) != 0;
const bool accepted = json::accept(text, comments, trailing_commas);
CHECK(nlohmann::json_document::accept(text, comments, trailing_commas) == accepted);
CHECK(nlohmann::json_document::accept(bytes, comments, trailing_commas) == accepted);
}
}
// a small deterministic generator of documents // a small deterministic generator of documents
struct generator struct generator
{ {
@@ -440,133 +423,6 @@ TEST_CASE("json_view builder")
} }
} }
SECTION("accept() without an index")
{
for (const char* text :
{
"", " ", "\n", "null", "nul", "true", "[", "]", "[]", "{}", "[1,2,3]", "[1,2,3", "[1,2,3,]", "{\"a\":1,}", "{\"a\"}", "{\"a\":}",
"[1] x", "[1][2]", "\xEF\xBB\xBF[1]", "[\"a\\", "[\"\\u12\"]", "[\"\\ud800\"]", "[\"\x01\"]", "[\"\xff\"]", "[01]", "[1.]",
"[.5]", "[-]", "[1e]", "// c\n[1]", "/* c */ {\"a\" /* x */ : 1}", "/* c", "[1] // c", "1e400", "-1e400", "123456789012345678901234567890",
"{\"a\":1,\"a\":2}", "[[[]]]", "\"\\u00e4\\n\\ud83d\\ude00\""
})
{
check_accept(text);
}
// a null byte inside the text, and after it
check_accept(std::string("[1,\0]", 5));
check_accept(std::string("[1]\0", 4));
check_accept(std::string("\0", 1));
// generated documents and damaged copies, as for the builder above
generator g;
for (int i = 0; i < 2000; ++i)
{
std::string text;
g.value(text, 0);
check_accept(text);
std::string damaged = text;
const auto at = static_cast<std::size_t>(g.r(static_cast<int>(damaged.size())));
static const char replacements[] = {'x', '"', '\\', ',', ':', ']', '}', '[', '{', '1', '-', '.', 'e', '\0', '\n', '/'}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
damaged[at] = replacements[g.r(16)];
check_accept(damaged);
check_accept(text.substr(0, at));
}
// nesting beyond the 64 levels that are kept on the builder's own stack, closed and not closed
for (const std::size_t depth :
{
63, 64, 65, 66, 200, 1000
})
{
check_accept(std::string(depth, '[') + std::string(depth, ']'));
check_accept(std::string(depth, '[') + std::string(depth - 1, ']'));
std::string objects;
for (std::size_t i = 0; i < depth; ++i)
{
objects += "{\"k\":";
}
objects += "1" + std::string(depth, '}');
check_accept(objects);
check_accept(objects.substr(0, objects.size() - 1));
}
// objects with enough members to get a hash index when they are indexed (and duplicate keys)
std::string large = "{";
for (int i = 0; i < 300; ++i)
{
large += (i == 0 ? "\"k" : ",\"k") + std::to_string(i % 200) + "\":" + std::to_string(i);
}
check_accept(large + "}");
check_accept(large);
check_accept("[" + large + "},{" + large.substr(1) + "}]");
// the overflow check uses the floating-point type of the document
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
for (const char* text :
{
"1e39", "-1e39", "3.4028235e38", "3.4028236e38", "[1.5e38, 3.5e38]", "{\"a\": 1e-50, \"b\": 1e39}", "340282356779733661637539395458142568448"
})
{
CAPTURE(text)
CHECK(nlohmann::basic_json_document<float_json>::accept(text) == float_json::accept(text));
}
// strings are checked and not decoded: runs of text of every length from 0 to 40 between valid and invalid
// escapes, UTF-8 sequences and control characters, in values and in keys, cut near the end of the text
const std::array<const char*, 9> valid = {{"\\n", "\\\"", "\\\\", "\\/", "\\t", "\\u00e4", "\\u20ac", "\\ud83d\\ude00", "\\u0000"}};
const std::array<const char*, 12> invalid = {{"\\x", "\\ud800", "\\udc00", "\\u12G4", "\\u12", "\\ud83dx", "\\ud83d\\u0041", "\\ud83d\\ud83d", "\\", "\x01", "\x1f", "\x7f"}};
const std::array<const char*, 6> utf8 = {{"\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xc3", "\xff", "\x80"}};
std::mt19937 rng(2029); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
for (std::size_t run = 0; run <= 40; ++run)
{
for (int i = 0; i < 30; ++i)
{
std::string content = std::string(rng() % 20, 'a');
const auto pieces = 1 + (rng() % 6);
for (unsigned k = 0; k < pieces; ++k)
{
content += valid[rng() % valid.size()];
content += std::string(k == 0 ? run : rng() % 41, static_cast<char>('b' + (rng() % 20)));
switch (rng() % 8)
{
case 0:
content += utf8[rng() % 3];
break;
case 1:
content += utf8[rng() % utf8.size()];
break;
case 2:
content += invalid[rng() % invalid.size()];
break;
default:
break;
}
}
for (const std::string& text :
{
"[\"" + content + "\"]", "{\"" + content + "\":1}", "[\"" + content, "[\"" + content.substr(0, content.size() - (rng() % 4)) + "\"]"
})
{
check_accept(text);
}
}
}
}
SECTION("escapes in a row")
{
for (const char* content :
{
"\\n", "\\n\\t\\\\", "\\u00e9\\u00e9\\u4e2d", "\\u00e9\\u00e", "\\u00e9\\", "\\n\\q", "\\n\\ud83d\\ude00\\n",
"\\ud83d\\ude00\\ud83d", "\\n\\n\\n abc \\n\\n", "\\n\x01", "\\n\xC3\x28", "\\n\\"
})
{
check_accept(std::string("[\"") + content + "\"]");
check_accept(std::string("[\"") + content);
check_accept(std::string("{\"") + content + "\":1}");
}
}
SECTION("test files") SECTION("test files")
{ {
for (const char* name : for (const char* name :
@@ -719,6 +575,59 @@ TEST_CASE("json_view builder: escapes between runs of text")
} }
} }
TEST_CASE("json_view builder: chains of unicode escapes")
{
// Consecutive \uXXXX escapes are decoded in a loop of their own. Chains of
// every length from 1 to 40 of escapes that give one, two, and three
// bytes of UTF-8, in upper and lower case, followed by what ends a chain:
// the end of the string, text, another escape, UTF-8, and everything that
// is not a valid escape (a bad digit at every position of the last
// escape, a lone surrogate, a surrogate pair, a cut in every place).
const std::array<const char*, 8> chain = {{"\\u0041", "\\u00e4", "\\u00E4", "\\u20ac", "\\u3042", "\\uFFFF", "\\u0000", "\\u07ff"}};
const std::array<const char*, 12> ends = {{"", "abc", "\\n", "\\u00e9", "\\ud83d\\ude00", "\\ud83d", "\\ude00", "\\ud83d\\u0041", "\\u12G4", "\\u12", "\xc3\xa4", "\\"}};
std::mt19937 rng(2027); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
for (std::size_t length = 1; length <= 40; ++length)
{
for (int i = 0; i < 20; ++i)
{
std::string content = std::string(rng() % 20, 'a');
for (std::size_t k = 0; k < length; ++k)
{
content += chain[rng() % chain.size()];
}
const std::string whole = content;
for (const char* end : ends)
{
check_string(whole + end);
check_string(whole + end + std::string(rng() % 19, 'z'));
}
// a bad digit at every position of every escape near the end of the chain
for (std::size_t back = 1; back <= 6 && back <= length; ++back)
{
for (std::size_t digit = 2; digit < 6; ++digit)
{
std::string broken = whole;
broken[broken.size() - (6 * back) + digit] = "gG\"\x01 z"[rng() % 6];
check_string(broken);
}
}
// the text cut in every place of the last three escapes
for (std::size_t cut = 0; cut <= 18 && cut <= whole.size(); ++cut)
{
check_string(whole.substr(0, whole.size() - cut));
}
}
}
// a chain longer than the first growth of the arena, in a key and in a value
std::string long_chain;
for (int i = 0; i < 3000; ++i)
{
long_chain += chain[static_cast<std::size_t>(i) % chain.size()];
}
check_string(long_chain);
check_string(long_chain + "\\ud83d\\ude00" + long_chain);
}
TEST_CASE("json_view node integer bits") TEST_CASE("json_view node integer bits")
{ {
using nlohmann::detail::view::integer_bits; using nlohmann::detail::view::integer_bits;
+24
View File
@@ -443,6 +443,30 @@ TEST_CASE("json_view from_json")
CHECK(d.root().materialize().get<point>().x == 3); CHECK(d.root().materialize().get<point>().x == 3);
} }
SECTION("escaped keys: members are found by their decoded names")
{
// keys are decoded before they are compared, as by parse(); a lookup
// that compares the raw bytes of the source would miss these members
const json_document d = json_document::parse(R"([{"\u0078": 1, "y": 2}, {"V\u0061lue": 3, "Te\u0078t": "t"}, {"\u0062": "escaped"}, {"x": 1, "\u0078": 3, "y": 2}])");
const json j = d.root().materialize();
CHECK(d.root()[0].get<point>().x == 1);
CHECK(d.root()[0].get<point>().y == 2);
CHECK((d.root()[0].get<point>() == j[0].get<point>()));
CHECK(d.root()[1].get<with_names>().value == 3);
CHECK(d.root()[1].get<with_names>().text == "t");
CHECK(d.root()[2].get<with_defaults>().b == "escaped");
CHECK(d.root()[2].get<with_defaults>().a == 1);
// a duplicate key written once plain and once escaped: still the first member
CHECK(d.root()[3].get<point>().x == 1);
CHECK(j[3].get<point>().x == 3);
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
// an escaped backslash is part of the decoded key: "x\\" is not "x"
const json_document e = json_document::parse(R"({"x\\": 1, "y": 2})");
CHECK(exception_of_call([&] { static_cast<void>(e.root().get<point>()); }) == exception_of_call([&] { static_cast<void>(e.root().materialize().get<point>()); }));
CHECK(exception_of_call([&] { static_cast<void>(e.root().get<point>()); }) == "[json.exception.out_of_range.403] key 'x' not found");
#endif
}
SECTION("editable documents and ordered_json") SECTION("editable documents and ordered_json")
{ {
json_editable_document e = json_editable_document::parse(R"({"x": 1, "y": 2})"); json_editable_document e = json_editable_document::parse(R"({"x": 1, "y": 2})");
+46
View File
@@ -23,6 +23,7 @@ using nlohmann::json;
#include <cstdint> #include <cstdint>
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <limits>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -248,6 +249,51 @@ TEST_CASE_TEMPLATE("serialization for extreme integer values", T, int32_t, uint3
} }
} }
TEST_CASE("integers of every digit count are written like std::to_string")
{
// dump_integer() writes up to 16 digits in two groups of eight and 17 to
// 20 digits as one to four digits and two groups of eight
std::vector<std::uint64_t> values;
std::uint64_t power = 1;
for (int digits = 1; digits <= 20; ++digits)
{
values.push_back(power); // 1 followed by zeros
values.push_back(power + 1);
values.push_back((power * 2) - 1);
if (digits < 20)
{
values.push_back((power * 10) - 1); // all 9s
values.push_back((power * 10) - power); // 9 followed by zeros
power *= 10;
}
}
values.push_back((std::numeric_limits<std::uint64_t>::max)());
values.push_back(static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()));
values.push_back(static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1);
std::uint64_t state = 0x9E3779B97F4A7C15u;
for (int i = 0; i < 20000; ++i)
{
state = (state * 6364136223846793005u) + 1442695040888963407u;
values.push_back(state >> (state % 61)); // every bit length
}
for (const auto v : values)
{
CAPTURE(v)
CHECK(json(v).dump() == std::to_string(v));
if (v <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
{
const auto s = static_cast<std::int64_t>(v);
CHECK(json(s).dump() == std::to_string(s));
CHECK(json(-s).dump() == std::to_string(-s));
}
}
CHECK(json((std::numeric_limits<std::int64_t>::min)()).dump() == "-9223372036854775808");
// in a container, so the digits are followed by other output
CHECK(json::array({std::uint64_t{18446744073709551615u}, std::int64_t{-1234567890123456789}, 1}).dump() == "[18446744073709551615,-1234567890123456789,1]");
}
TEST_CASE("dump with binary values") TEST_CASE("dump with binary values")
{ {
auto binary = json::binary({1, 2, 3, 4}); auto binary = json::binary({1, 2, 3, 4});