From e46a982be49b00ca04a2f541958e61f1f0680208 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 2 Nov 2026 06:59:16 +0100 Subject: [PATCH] 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(): 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(): 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 --- include/nlohmann/detail/view/builder.hpp | 3 +- include/nlohmann/detail/view/compare.hpp | 8 ++--- include/nlohmann/detail/view/edit_storage.hpp | 2 +- include/nlohmann/detail/view/errors.hpp | 4 --- include/nlohmann/detail/view/image.hpp | 4 --- include/nlohmann/detail/view/serializer.hpp | 14 +++----- single_include/nlohmann/json_view.hpp | 35 +++++++------------ 7 files changed, 24 insertions(+), 46 deletions(-) diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index 938d6d73b..283cae075 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -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 diff --git a/include/nlohmann/detail/view/compare.hpp b/include/nlohmann/detail/view/compare.hpp index 239871f01..2d2c238b9 100644 --- a/include/nlohmann/detail/view/compare.hpp +++ b/include/nlohmann/detail/view/compare.hpp @@ -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 auto& s = *m_json->template get_ptr(); return string_view_t(s.data(), s.size()); } diff --git a/include/nlohmann/detail/view/edit_storage.hpp b/include/nlohmann/detail/view/edit_storage.hpp index 10898f99f..34b804c46 100644 --- a/include/nlohmann/detail/view/edit_storage.hpp +++ b/include/nlohmann/detail/view/edit_storage.hpp @@ -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(e.moved.size() - 1); diff --git a/include/nlohmann/detail/view/errors.hpp b/include/nlohmann/detail/view/errors.hpp index bc86dcf83..c7cf6dce0 100644 --- a/include/nlohmann/detail/view/errors.hpp +++ b/include/nlohmann/detail/view/errors.hpp @@ -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; diff --git a/include/nlohmann/detail/view/image.hpp b/include/nlohmann/detail/view/image.hpp index 9e57082de..e6680730f 100644 --- a/include/nlohmann/detail/view/image.hpp +++ b/include/nlohmann/detail/view/image.hpp @@ -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(); diff --git a/include/nlohmann/detail/view/serializer.hpp b/include/nlohmann/detail/view/serializer.hpp index fe6e0a6b1..83dc4570a 100644 --- a/include/nlohmann/detail/view/serializer.hpp +++ b/include/nlohmann/detail/view/serializer.hpp @@ -641,7 +641,7 @@ class view_serializer { invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8) } - write_codepoint(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(dumped); } // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8) - template - 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(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) diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index c9adec745..9be59e902 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -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 auto& s = *m_json->template get_ptr(); 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(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(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(dumped); } // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8) - template - 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(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)