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
7 changed files with 24 additions and 46 deletions

No files matched your search

+2 -1
View File
@@ -1174,8 +1174,9 @@ indent_done:
{ {
++p; ++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); p = skip_digits(p + 1, e);
} }
else else
+4 -4
View File
@@ -397,9 +397,9 @@ class view_side
return m_view.size(); 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 bool boolean() const noexcept
@@ -522,9 +522,9 @@ class json_side
return m_json->size(); 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()); 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; e.moved_cap[n->off] = cap;
return; 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.push_back(seq);
e.moved_cap.push_back(cap); e.moved_cap.push_back(cap);
n->off = static_cast<std::uint32_t>(e.moved.size() - 1); 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; end = p.end;
return; 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 // 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 closing = 0;
std::size_t pos = std::string::npos; 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.edits.reset();
d.base[2] = nullptr; d.base[2] = nullptr;
d.owned.clear(); d.owned.clear();
if (d.owned_image.empty() || image != d.owned_image.data())
{
d.owned_image.clear();
}
d.arena.clear(); d.arena.clear();
d.indexes.clear(); d.indexes.clear();
d.index_slots.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) 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; ++i;
} }
m_out.put('"'); m_out.put('"');
@@ -1489,8 +1489,9 @@ class view_serializer
static_cast<void>(dumped); static_cast<void>(dumped);
} // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8) } // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8)
template<bool EnsureAscii> /// write the escape sequence of a quote, a backslash or a control
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len) /// character (the only bytes a run of plain output stops at)
void write_escaped(std::uint32_t codepoint)
{ {
switch (codepoint) switch (codepoint)
{ {
@@ -1518,12 +1519,7 @@ class view_serializer
default: default:
break; break;
} }
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F)) write_u_escape(codepoint); // (a control character)
{
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)
} }
void write_u_escape(std::uint32_t u) void write_u_escape(std::uint32_t u)
+12 -23
View File
@@ -2479,8 +2479,9 @@ indent_done:
{ {
++p; ++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); p = skip_digits(p + 1, e);
} }
else else
@@ -3549,9 +3550,9 @@ class view_side
return m_view.size(); 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 bool boolean() const noexcept
@@ -3674,9 +3675,9 @@ class json_side
return m_json->size(); 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()); 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; end = p.end;
return; 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 // 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 closing = 0;
std::size_t pos = std::string::npos; 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; e.moved_cap[n->off] = cap;
return; 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.push_back(seq);
e.moved_cap.push_back(cap); e.moved_cap.push_back(cap);
n->off = static_cast<std::uint32_t>(e.moved.size() - 1); 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.edits.reset();
d.base[2] = nullptr; d.base[2] = nullptr;
d.owned.clear(); d.owned.clear();
if (d.owned_image.empty() || image != d.owned_image.data())
{
d.owned_image.clear();
}
d.arena.clear(); d.arena.clear();
d.indexes.clear(); d.indexes.clear();
d.index_slots.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) 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; ++i;
} }
m_out.put('"'); m_out.put('"');
@@ -9941,8 +9934,9 @@ class view_serializer
static_cast<void>(dumped); static_cast<void>(dumped);
} // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8) } // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8)
template<bool EnsureAscii> /// write the escape sequence of a quote, a backslash or a control
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len) /// character (the only bytes a run of plain output stops at)
void write_escaped(std::uint32_t codepoint)
{ {
switch (codepoint) switch (codepoint)
{ {
@@ -9970,12 +9964,7 @@ class view_serializer
default: default:
break; break;
} }
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F)) write_u_escape(codepoint); // (a control character)
{
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)
} }
void write_u_escape(std::uint32_t u) void write_u_escape(std::uint32_t u)