Compare commits

..
Author SHA1 Message Date
Niels Lohmann e46a982be4 json_view: remove checks that invariants make dead
Branch coverage of the test suite and of 24k mutated inputs showed
these conditions as never taken; each is implied by the code around it:

- view_serializer::write_codepoint<EnsureAscii>(): the only caller
  passes the byte a run of plain output stopped at (a quote, a
  backslash or a control character), so the function is now
  write_escaped(), which writes that byte's escape sequence
- load_image(): `owned_image.empty() || image != owned_image.data()`;
  both callers hand in a fresh document_data, and for load(vector&&)
  the image is owned_image itself
- set_moved(): reserve_moved(); all three callers reserved first or
  have the moved flag set (the documented precondition)
- view_side::string() and json_side::string() in equal(): only used
  after both sides were found to be strings, so they read the string
  directly (and are noexcept)
- number<false>(): the '1'..'9' test after a sign-less dispatch, which
  only happens for a digit
- value_range(): the pre-loop `is_new || off >= size` test, which the
  loop's first pass repeats on the same node

No change in behaviour: a build that aborts on each removed side passes
the test suite and the fuzz corpora.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 10:05:55 +01:00
Niels Lohmann 73b0a3aba3 Drop the null_result parameter JSON_IMPLEMENT_OPERATOR no longer uses
Without the same-type scalar cases, the macro body has no use for it, and
JSON_IMPLEMENT_SCALAR_CASES is now used only by JSON_IMPLEMENT_SCALAR_FAST.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 10:05:55 +01:00
Niels Lohmann 7196bc5b22 iter_impl: keep the type-dependent initialization in the constructor
The check is not needed for correctness (m_it is value-initialized), but
without it GCC 13 generates slower iteration loops: a recursive walk over
a parsed document takes 0.5-2.7 % longer (callgrind: +1.9 % instructions
on twitter.json, +0.95 % on canada.json). Clang generates identical code
either way. See the measurements in #6005.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 10:05:54 +01:00
Niels Lohmann 2ac0851157 basic_json, iterators, serializer: remove checks that invariants make dead
Branch coverage of the test suite and of 24k mutated inputs showed
these conditions as never taken; each is implied by the code around it:

- copy_object_level(): `src_it != src_object.cend()`; dst was built from
  exactly src_object.size() unique keys and src_it advances at most once
  per member
- the general comparison operators (equal_general, less_general,
  three_way_general): the five same-type scalar cases, which the inline
  fast path of every operator handles before calling them (now an
  assert); and the NaN half of compares_unordered() in the mixed-type
  branch, reached only after all six number pairs returned, where only
  a discarded operand is unordered
- serializer, ensure_ascii: the reset of `bytes` under error_handler_t::keep
  and the surrogate-pair escape in the UTF8_ACCEPT case; every
  well-formed multi-byte character is escaped by write_u_escapes() before
  decode() sees it, so decode() only ever accepts DEL here, a single byte
  of which nothing was buffered (now asserts)
- iter_impl(pointer): the if/else chain assigned value-initialized
  iterators to the value-initialized m_it

No change in behaviour: a build that aborts on each removed side passes
the test suite, the fuzz corpora and 23.2M dumps of all 1-, 2- and
boundary 4-byte strings in both ensure_ascii modes.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 09:41:01 +01:00
Niels Lohmann f093573828 Lexer, parser, stream adapter, SAX: remove checks that invariants make dead
Branch coverage of the test suite and of 24k mutated inputs showed
these conditions as never taken; each is implied by the code around it:

- lexer: `next_unget` in get_codepoint_bulk() and scan_literal_bulk().
  unget() is only called at the end of scan_number(), in scan_comment()
  and in skip_bom(); every token scan starts with a plain get() in
  skip_whitespace() that consumes a pending unget, and nothing between
  that get() and the bulk helpers ungets (now asserts, as in
  get_after_string_run())
- lexer: `number_type == value_integer` in convert_integer(): both
  callers exclude value_float and the type has three values
- parser: `!context.empty()` in exception_message(): all callers pass a
  literal
- input_stream_adapter: `refill` in get_character_slow(): the function
  is only called after get_character() found the get area exhausted,
  and the variable recomputed that comparison
- json_sax_dom_callback_parser: `ref_stack.back()` in key() (the null
  case returned a few lines earlier, and the callback in between cannot
  reach ref_stack), `is_structured()` in end_object() (non-null entries
  are only pushed for objects and arrays), `!key_stack.empty()` in
  current_key() (a kept object pushed its key before any value)

No change in behaviour: a build that aborts on each removed side passes
the test suite, the fuzz corpora and 40k random documents through every
input adapter in four macro configurations.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 09:41:00 +01:00
17 changed files with 114 additions and 148 deletions

No files matched your search

@@ -545,12 +545,9 @@ class input_stream_adapter
std::char_traits<char>::int_type get_character_slow()
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
save_token_segment();
}
// the get area is exhausted (get_character() handles the other case),
// so sbumpc() refills it
save_token_segment();
#endif
auto res = sb->sbumpc();
@@ -563,17 +560,13 @@ class input_stream_adapter
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
else
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true; // LCOV_EXCL_LINE (called only when the get area is exhausted)
}
#endif
return res;
}
+3 -4
View File
@@ -1737,7 +1737,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
key_stack.push_back(val);
// add discarded value at the given key and store the reference for later
if (keep && ref_stack.back())
if (keep)
{
auto& obj = *ref_stack.back()->m_data.m_value.object;
// a single lookup: insert the placeholder, or find the existing member
@@ -1794,7 +1794,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
const string_t object_key = pop_container();
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
if (!ref_stack.empty() && ref_stack.back())
{
// remove discarded value
remove_discarded_value(*ref_stack.back(), object_key);
@@ -1960,8 +1960,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
*/
string_t current_key() const
{
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()
&& !key_stack.empty())
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
{
return key_stack.back();
}
+13 -8
View File
@@ -300,14 +300,16 @@ class lexer : public lexer_base<BasicJsonType>
/// calls would (including the position and token bookkeeping of a
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
/// the 4 digits, just as the last such get() would; the codepoint is
/// written to @a out. Makes no state change and returns false for a
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
/// being a hex digit, so the caller falls back unchanged to the
/// per-character loop, which then reports the same diagnostic (stopping
/// at the first invalid digit) as before this optimization.
/// written to @a out. Makes no state change and returns false for fewer
/// than 4 remaining bytes or any of the 4 bytes not being a hex digit, so
/// the caller falls back unchanged to the per-character loop, which then
/// reports the same diagnostic (stopping at the first invalid digit) as
/// before this optimization.
bool get_codepoint_bulk(std::true_type /*bulk*/, int& out)
{
if (next_unget || ia.bulk_remaining() < 4)
// no unget is pending inside a string (see get_after_string_run())
JSON_ASSERT(!next_unget);
if (ia.bulk_remaining() < 4)
{
return false;
}
@@ -1409,7 +1411,7 @@ scan_number_done:
return token_type::value_unsigned;
}
}
else if (number_type == token_type::value_integer)
else // value_integer (the callers handle value_float themselves)
{
if (parse_integer_signed(first, last, value_integer))
{
@@ -1780,7 +1782,10 @@ scan_number_done:
/// the error at the right character
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
{
if (next_unget || ia.bulk_remaining() < length
// called right after scan_after_whitespace() read current (see
// get_after_string_run()), so no unget is pending
JSON_ASSERT(!next_unget);
if (ia.bulk_remaining() < length
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
{
return false;
+1 -4
View File
@@ -575,10 +575,7 @@ class parser
{
std::string error_msg = "syntax error ";
if (!context.empty())
{
error_msg += concat("while parsing ", context, ' ');
}
error_msg += concat("while parsing ", context, ' ');
error_msg += "- ";
@@ -108,6 +108,9 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{
JSON_ASSERT(m_object != nullptr);
// m_it is already value-initialized, so this is not needed for
// correctness; it is kept because GCC generates faster loops with it
// (iterating a parsed document is 0.5-2.7 % slower without it, #6005)
if (m_object->is_object())
{
m_it.object_iterator = typename object_t::iterator();
+5 -12
View File
@@ -1113,21 +1113,14 @@ class serializer
{
// 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
// copies). Under EnsureAscii it is DEL (0x7F): every
// well-formed multi-byte character is escaped by
// write_u_escapes() above, so decode() only ever accepts a
// single byte here, and nothing of it was buffered
if (EnsureAscii)
{
JSON_ASSERT(codepoint == 0x7F);
if (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;
}
JSON_ASSERT(bytes == bytes_after_last_accept);
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
}
else
+2 -1
View File
@@ -1174,8 +1174,9 @@ indent_done:
{
++p;
}
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
else if (!negative || NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
// (without a sign, the dispatch saw a digit)
p = skip_digits(p + 1, e);
}
else
+4 -4
View File
@@ -397,9 +397,9 @@ class view_side
return m_view.size();
}
string_view_t string() const
string_view_t string() const noexcept
{
return m_view.get_string();
return {m_view.m_doc->str(*m_view.m_node), m_view.m_node->len};
}
bool boolean() const noexcept
@@ -522,9 +522,9 @@ class json_side
return m_json->size();
}
string_view_t string() const
string_view_t string() const noexcept
{
const auto& s = m_json->template get_ref<const typename BasicJsonType::string_t&>();
const auto& s = *m_json->template get_ptr<const typename BasicJsonType::string_t*>();
return string_view_t(s.data(), s.size());
}
@@ -158,7 +158,7 @@ inline void set_moved(document_data& d, node* n, node* seq, std::size_t cap)
e.moved_cap[n->off] = cap;
return;
}
reserve_moved(d);
// every caller has called reserve_moved() (see above), so neither push_back() can throw
e.moved.push_back(seq);
e.moved_cap.push_back(cap);
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
-4
View File
@@ -185,10 +185,6 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
end = p.end;
return;
}
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
{
return;
}
// the open brackets: n, then the last element as long as it is a non-empty container
std::size_t closing = 0;
std::size_t pos = std::string::npos;
-4
View File
@@ -787,10 +787,6 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
d.edits.reset();
d.base[2] = nullptr;
d.owned.clear();
if (d.owned_image.empty() || image != d.owned_image.data())
{
d.owned_image.clear();
}
d.arena.clear();
d.indexes.clear();
d.index_slots.clear();
+5 -9
View File
@@ -641,7 +641,7 @@ class view_serializer
{
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
}
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
write_escaped(s[i]); // a quote, a backslash, or a control character
++i;
}
m_out.put('"');
@@ -1489,8 +1489,9 @@ class view_serializer
static_cast<void>(dumped);
} // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8)
template<bool EnsureAscii>
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
/// write the escape sequence of a quote, a backslash or a control
/// character (the only bytes a run of plain output stops at)
void write_escaped(std::uint32_t codepoint)
{
switch (codepoint)
{
@@ -1518,12 +1519,7 @@ class view_serializer
default:
break;
}
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F))
{
write_u_escape(codepoint); // (a single byte: the only caller passes s[i])
return;
}
m_out.put(reinterpret_cast<const char*>(bytes), len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) LCOV_EXCL_LINE (printable characters are copied in runs)
write_u_escape(codepoint); // (a control character)
}
void write_u_escape(std::uint32_t u)
+15 -10
View File
@@ -1576,7 +1576,7 @@ public:
auto src_it = src_object.cbegin();
for (auto& element : *dst.m_data.m_value.object)
{
if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && copy_keys_equal(src_it->first, element.first)))
if (JSON_HEDLEY_LIKELY(copy_keys_equal(src_it->first, element.first)))
{
copy_shallow(src_it->second, element.second, worklist);
++src_it;
@@ -5353,8 +5353,7 @@ public:
/// @name lexicographical comparison operators
/// @{
// the same-type cases for non-owning scalars, shared by JSON_IMPLEMENT_OPERATOR
// and JSON_IMPLEMENT_SCALAR_FAST
// the same-type cases for non-owning scalars, used by JSON_IMPLEMENT_SCALAR_FAST
#define JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::null: \
return (null_result); \
@@ -5376,7 +5375,7 @@ public:
// because any negative signed value is smaller than any unsigned value.
// Otherwise, the non-negative signed value is cast to unsigned before the
// comparison to avoid wraparound.
#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result, deep_result, may_descend) \
#define JSON_IMPLEMENT_OPERATOR(op, unordered_result, default_result, deep_result, may_descend) \
const auto lhs_type = lhs.type(); \
const auto rhs_type = rhs.type(); \
\
@@ -5404,8 +5403,15 @@ public:
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
} \
\
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
\
/* same-type scalars never get here: the inline fast path of the operator handles them */ \
case value_t::null: \
case value_t::boolean: \
case value_t::number_integer: \
case value_t::number_unsigned: \
case value_t::number_float: \
JSON_ASSERT(false); /* NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) */ \
return (unordered_result); \
\
case value_t::string: \
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \
\
@@ -5445,7 +5451,7 @@ public:
? (number_integer_t(-1) op number_integer_t(1)) \
: (static_cast<number_unsigned_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_unsigned); \
} \
else if(compares_unordered(lhs, rhs))\
else if(lhs_type == value_t::discarded || rhs_type == value_t::discarded)\
{\
return (unordered_result);\
}\
@@ -5513,7 +5519,7 @@ public:
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
JSON_IMPLEMENT_OPERATOR( ==, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
@@ -5527,7 +5533,6 @@ public:
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
@@ -5539,7 +5544,7 @@ public:
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
JSON_IMPLEMENT_OPERATOR( <, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
}
#endif
+44 -49
View File
@@ -9652,12 +9652,9 @@ class input_stream_adapter
std::char_traits<char>::int_type get_character_slow()
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
save_token_segment();
}
// the get area is exhausted (get_character() handles the other case),
// so sbumpc() refills it
save_token_segment();
#endif
auto res = sb->sbumpc();
@@ -9670,17 +9667,13 @@ class input_stream_adapter
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
else
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true; // LCOV_EXCL_LINE (called only when the get area is exhausted)
}
#endif
return res;
}
@@ -13334,14 +13327,16 @@ class lexer : public lexer_base<BasicJsonType>
/// calls would (including the position and token bookkeeping of a
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
/// the 4 digits, just as the last such get() would; the codepoint is
/// written to @a out. Makes no state change and returns false for a
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
/// being a hex digit, so the caller falls back unchanged to the
/// per-character loop, which then reports the same diagnostic (stopping
/// at the first invalid digit) as before this optimization.
/// written to @a out. Makes no state change and returns false for fewer
/// than 4 remaining bytes or any of the 4 bytes not being a hex digit, so
/// the caller falls back unchanged to the per-character loop, which then
/// reports the same diagnostic (stopping at the first invalid digit) as
/// before this optimization.
bool get_codepoint_bulk(std::true_type /*bulk*/, int& out)
{
if (next_unget || ia.bulk_remaining() < 4)
// no unget is pending inside a string (see get_after_string_run())
JSON_ASSERT(!next_unget);
if (ia.bulk_remaining() < 4)
{
return false;
}
@@ -14443,7 +14438,7 @@ scan_number_done:
return token_type::value_unsigned;
}
}
else if (number_type == token_type::value_integer)
else // value_integer (the callers handle value_float themselves)
{
if (parse_integer_signed(first, last, value_integer))
{
@@ -14814,7 +14809,10 @@ scan_number_done:
/// the error at the right character
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
{
if (next_unget || ia.bulk_remaining() < length
// called right after scan_after_whitespace() read current (see
// get_after_string_run()), so no unget is pending
JSON_ASSERT(!next_unget);
if (ia.bulk_remaining() < length
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
{
return false;
@@ -17371,7 +17369,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
key_stack.push_back(val);
// add discarded value at the given key and store the reference for later
if (keep && ref_stack.back())
if (keep)
{
auto& obj = *ref_stack.back()->m_data.m_value.object;
// a single lookup: insert the placeholder, or find the existing member
@@ -17428,7 +17426,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
const string_t object_key = pop_container();
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
if (!ref_stack.empty() && ref_stack.back())
{
// remove discarded value
remove_discarded_value(*ref_stack.back(), object_key);
@@ -17594,8 +17592,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
*/
string_t current_key() const
{
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()
&& !key_stack.empty())
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
{
return key_stack.back();
}
@@ -23739,10 +23736,7 @@ class parser
{
std::string error_msg = "syntax error ";
if (!context.empty())
{
error_msg += concat("while parsing ", context, ' ');
}
error_msg += concat("while parsing ", context, ' ');
error_msg += "- ";
@@ -24070,6 +24064,9 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{
JSON_ASSERT(m_object != nullptr);
// m_it is already value-initialized, so this is not needed for
// correctness; it is kept because GCC generates faster loops with it
// (iterating a parsed document is 0.5-2.7 % slower without it, #6005)
if (m_object->is_object())
{
m_it.object_iterator = typename object_t::iterator();
@@ -36273,21 +36270,14 @@ class serializer
{
// 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
// copies). Under EnsureAscii it is DEL (0x7F): every
// well-formed multi-byte character is escaped by
// write_u_escapes() above, so decode() only ever accepts a
// single byte here, and nothing of it was buffered
if (EnsureAscii)
{
JSON_ASSERT(codepoint == 0x7F);
if (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;
}
JSON_ASSERT(bytes == bytes_after_last_accept);
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
}
else
@@ -39311,7 +39301,7 @@ public:
auto src_it = src_object.cbegin();
for (auto& element : *dst.m_data.m_value.object)
{
if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && copy_keys_equal(src_it->first, element.first)))
if (JSON_HEDLEY_LIKELY(copy_keys_equal(src_it->first, element.first)))
{
copy_shallow(src_it->second, element.second, worklist);
++src_it;
@@ -43088,8 +43078,7 @@ public:
/// @name lexicographical comparison operators
/// @{
// the same-type cases for non-owning scalars, shared by JSON_IMPLEMENT_OPERATOR
// and JSON_IMPLEMENT_SCALAR_FAST
// the same-type cases for non-owning scalars, used by JSON_IMPLEMENT_SCALAR_FAST
#define JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::null: \
return (null_result); \
@@ -43111,7 +43100,7 @@ public:
// because any negative signed value is smaller than any unsigned value.
// Otherwise, the non-negative signed value is cast to unsigned before the
// comparison to avoid wraparound.
#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result, deep_result, may_descend) \
#define JSON_IMPLEMENT_OPERATOR(op, unordered_result, default_result, deep_result, may_descend) \
const auto lhs_type = lhs.type(); \
const auto rhs_type = rhs.type(); \
\
@@ -43139,8 +43128,15 @@ public:
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
} \
\
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
\
/* same-type scalars never get here: the inline fast path of the operator handles them */ \
case value_t::null: \
case value_t::boolean: \
case value_t::number_integer: \
case value_t::number_unsigned: \
case value_t::number_float: \
JSON_ASSERT(false); /* NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) */ \
return (unordered_result); \
\
case value_t::string: \
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \
\
@@ -43180,7 +43176,7 @@ public:
? (number_integer_t(-1) op number_integer_t(1)) \
: (static_cast<number_unsigned_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_unsigned); \
} \
else if(compares_unordered(lhs, rhs))\
else if(lhs_type == value_t::discarded || rhs_type == value_t::discarded)\
{\
return (unordered_result);\
}\
@@ -43248,7 +43244,7 @@ public:
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
JSON_IMPLEMENT_OPERATOR( ==, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
@@ -43262,7 +43258,6 @@ public:
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
@@ -43274,7 +43269,7 @@ public:
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
JSON_IMPLEMENT_OPERATOR( <, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
}
#endif
+12 -23
View File
@@ -2479,8 +2479,9 @@ indent_done:
{
++p;
}
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
else if (!negative || NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
{
// (without a sign, the dispatch saw a digit)
p = skip_digits(p + 1, e);
}
else
@@ -3549,9 +3550,9 @@ class view_side
return m_view.size();
}
string_view_t string() const
string_view_t string() const noexcept
{
return m_view.get_string();
return {m_view.m_doc->str(*m_view.m_node), m_view.m_node->len};
}
bool boolean() const noexcept
@@ -3674,9 +3675,9 @@ class json_side
return m_json->size();
}
string_view_t string() const
string_view_t string() const noexcept
{
const auto& s = m_json->template get_ref<const typename BasicJsonType::string_t&>();
const auto& s = *m_json->template get_ptr<const typename BasicJsonType::string_t*>();
return string_view_t(s.data(), s.size());
}
@@ -5013,10 +5014,6 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
end = p.end;
return;
}
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
{
return;
}
// the open brackets: n, then the last element as long as it is a non-empty container
std::size_t closing = 0;
std::size_t pos = std::string::npos;
@@ -5476,7 +5473,7 @@ inline void set_moved(document_data& d, node* n, node* seq, std::size_t cap)
e.moved_cap[n->off] = cap;
return;
}
reserve_moved(d);
// every caller has called reserve_moved() (see above), so neither push_back() can throw
e.moved.push_back(seq);
e.moved_cap.push_back(cap);
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
@@ -7780,10 +7777,6 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
d.edits.reset();
d.base[2] = nullptr;
d.owned.clear();
if (d.owned_image.empty() || image != d.owned_image.data())
{
d.owned_image.clear();
}
d.arena.clear();
d.indexes.clear();
d.index_slots.clear();
@@ -9093,7 +9086,7 @@ class view_serializer
{
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
}
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
write_escaped(s[i]); // a quote, a backslash, or a control character
++i;
}
m_out.put('"');
@@ -9941,8 +9934,9 @@ class view_serializer
static_cast<void>(dumped);
} // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8)
template<bool EnsureAscii>
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
/// write the escape sequence of a quote, a backslash or a control
/// character (the only bytes a run of plain output stops at)
void write_escaped(std::uint32_t codepoint)
{
switch (codepoint)
{
@@ -9970,12 +9964,7 @@ class view_serializer
default:
break;
}
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F))
{
write_u_escape(codepoint); // (a single byte: the only caller passes s[i])
return;
}
m_out.put(reinterpret_cast<const char*>(bytes), len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) LCOV_EXCL_LINE (printable characters are copied in runs)
write_u_escape(codepoint); // (a control character)
}
void write_u_escape(std::uint32_t u)
+2 -3
View File
@@ -534,10 +534,9 @@ TEST_CASE("object_key_index")
// the hashes are not distinct on purpose: elements with the same
// hash are told apart by the caller
const auto hash_of = [](std::size_t position) -> std::size_t
const auto hash_of = [](std::size_t position)
{
// truncated to 32 bits where std::size_t is that small
return static_cast<std::size_t>((position % 50) * 0x9E3779B97F4A7C15ull);
return (position % 50) * 0x9E3779B97F4A7C15ull;
};
std::vector<std::size_t> hashes;
-1
View File
@@ -1555,7 +1555,6 @@ TEST_CASE("regression tests 1")
{
my_json j = make();
#ifdef JSON_NO_THREAD_LOCAL
// (set by the user, or by the library for clang with MinGW)
// the objects below the outermost one are not copied by their
// object_t here, but member by member, which keeps their
// timestamps correct: "c" is not mistaken for "b" and is inserted