Compare commits

...
Author SHA1 Message Date
Niels Lohmann 15348ea128 Fix develop CI: GCC 4.8, Clang 3.4 and clang-tidy errors
- unit-json_patch.cpp: move multi-line raw strings out of CHECK(), which
  GCC 4.8 cannot parse inside a macro argument
- unit-recursion_depth_limit.cpp: value-initialize the const std::hash
  (Clang 3.4 rejects default initialization of a const object)
- json_view: silence modernize-use-transparent-functors (std::less<> is
  C++14), bugprone-suspicious-stringview-data-usage (the length comes from
  the node) and the deliberate use-after-clear() / owning-memory hits in
  unit-json_view_builder.cpp; use a raw string literal there

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-12 12:15:54 +01:00
6 changed files with 22 additions and 12 deletions

No files matched your search

+2
View File
@@ -905,6 +905,8 @@ void resume_array(const lockstep_path& /*path*/, const A& /*a*/, const B& /*b*/,
template<typename BasicJsonType, typename ViewA, typename ViewB> template<typename BasicJsonType, typename ViewA, typename ViewB>
bool floats_equal(const view_side<BasicJsonType, ViewA>& a, const view_side<BasicJsonType, ViewB>& b) bool floats_equal(const view_side<BasicJsonType, ViewA>& a, const view_side<BasicJsonType, ViewB>& b)
{ {
// float_token_is_nan() reads at most the node length of the token, which is not null terminated
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
return (a.value_node()->extra == b.value_node()->extra && a.float_token() == b.float_token() && !(view_side<BasicJsonType, ViewA>::nav::editable() && float_token_is_nan(*a.value_node(), a.float_token().data()))) || std::equal_to<typename BasicJsonType::number_float_t>()(a.floating(), b.floating()); return (a.value_node()->extra == b.value_node()->extra && a.float_token() == b.float_token() && !(view_side<BasicJsonType, ViewA>::nav::editable() && float_token_is_nan(*a.value_node(), a.float_token().data()))) || std::equal_to<typename BasicJsonType::number_float_t>()(a.floating(), b.floating());
} }
@@ -158,7 +158,8 @@ struct document_data
{ {
const auto overlaps = [p, n](const char* b, std::size_t m) noexcept const auto overlaps = [p, n](const char* b, std::size_t m) noexcept
{ {
// (std::less: a total order also for pointers into different objects) // (std::less: a total order also for pointers into different objects; std::less<> needs C++14)
// NOLINTNEXTLINE(modernize-use-transparent-functors)
return n != 0 && m != 0 && std::less<const char*>()(p, b + m) && std::less<const char*>()(b, p + n); return n != 0 && m != 0 && std::less<const char*>()(p, b + m) && std::less<const char*>()(b, p + n);
}; };
return overlaps(owned.data(), owned.capacity()) return overlaps(owned.data(), owned.capacity())
+4 -1
View File
@@ -502,7 +502,8 @@ struct document_data
{ {
const auto overlaps = [p, n](const char* b, std::size_t m) noexcept const auto overlaps = [p, n](const char* b, std::size_t m) noexcept
{ {
// (std::less: a total order also for pointers into different objects) // (std::less: a total order also for pointers into different objects; std::less<> needs C++14)
// NOLINTNEXTLINE(modernize-use-transparent-functors)
return n != 0 && m != 0 && std::less<const char*>()(p, b + m) && std::less<const char*>()(b, p + n); return n != 0 && m != 0 && std::less<const char*>()(p, b + m) && std::less<const char*>()(b, p + n);
}; };
return overlaps(owned.data(), owned.capacity()) return overlaps(owned.data(), owned.capacity())
@@ -4088,6 +4089,8 @@ void resume_array(const lockstep_path& /*path*/, const A& /*a*/, const B& /*b*/,
template<typename BasicJsonType, typename ViewA, typename ViewB> template<typename BasicJsonType, typename ViewA, typename ViewB>
bool floats_equal(const view_side<BasicJsonType, ViewA>& a, const view_side<BasicJsonType, ViewB>& b) bool floats_equal(const view_side<BasicJsonType, ViewA>& a, const view_side<BasicJsonType, ViewB>& b)
{ {
// float_token_is_nan() reads at most the node length of the token, which is not null terminated
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
return (a.value_node()->extra == b.value_node()->extra && a.float_token() == b.float_token() && !(view_side<BasicJsonType, ViewA>::nav::editable() && float_token_is_nan(*a.value_node(), a.float_token().data()))) || std::equal_to<typename BasicJsonType::number_float_t>()(a.floating(), b.floating()); return (a.value_node()->extra == b.value_node()->extra && a.float_token() == b.float_token() && !(view_side<BasicJsonType, ViewA>::nav::editable() && float_token_is_nan(*a.value_node(), a.float_token().data()))) || std::equal_to<typename BasicJsonType::number_float_t>()(a.floating(), b.floating());
} }
+9 -6
View File
@@ -2682,25 +2682,27 @@ TEST_CASE("JSON patch - consecutive operations with paths that share a prefix")
const json doc = json::parse(R"({"a": [{"b": [1, 2, 3]}, {"b": [4, 5, 6]}], "c": {"d": {"e": 7}}})"); const json doc = json::parse(R"({"a": [{"b": [1, 2, 3]}, {"b": [4, 5, 6]}], "c": {"d": {"e": 7}}})");
// an element inserted before the elements of an array moves them // an element inserted before the elements of an array moves them
CHECK(doc.patch(json::parse(R"([ const json inserted = json::parse(R"([
{"op": "test", "path": "/a/0/b/2", "value": 3}, {"op": "test", "path": "/a/0/b/2", "value": 3},
{"op": "add", "path": "/a/0", "value": {"b": [9]}}, {"op": "add", "path": "/a/0", "value": {"b": [9]}},
{"op": "test", "path": "/a/1/b/2", "value": 3}, {"op": "test", "path": "/a/1/b/2", "value": 3},
{"op": "test", "path": "/a/0/b/0", "value": 9}, {"op": "test", "path": "/a/0/b/0", "value": 9},
{"op": "test", "path": "/a/2/b/0", "value": 4} {"op": "test", "path": "/a/2/b/0", "value": 4}
])")) == json::parse(R"({"a": [{"b": [9]}, {"b": [1, 2, 3]}, {"b": [4, 5, 6]}], "c": {"d": {"e": 7}}})")); ])");
CHECK(doc.patch(inserted) == json::parse(R"({"a": [{"b": [9]}, {"b": [1, 2, 3]}, {"b": [4, 5, 6]}], "c": {"d": {"e": 7}}})"));
// an element removed from the front // an element removed from the front
CHECK(doc.patch(json::parse(R"([ const json removed_front = json::parse(R"([
{"op": "test", "path": "/a/1/b/2", "value": 6}, {"op": "test", "path": "/a/1/b/2", "value": 6},
{"op": "remove", "path": "/a/0"}, {"op": "remove", "path": "/a/0"},
{"op": "test", "path": "/a/0/b/2", "value": 6}, {"op": "test", "path": "/a/0/b/2", "value": 6},
{"op": "replace", "path": "/a/0/b/2", "value": 60}, {"op": "replace", "path": "/a/0/b/2", "value": 60},
{"op": "test", "path": "/a/0/b/2", "value": 60} {"op": "test", "path": "/a/0/b/2", "value": 60}
])")) == json::parse(R"({"a": [{"b": [4, 5, 60]}], "c": {"d": {"e": 7}}})")); ])");
CHECK(doc.patch(removed_front) == json::parse(R"({"a": [{"b": [4, 5, 60]}], "c": {"d": {"e": 7}}})"));
// a value replaced above the path that was resolved before // a value replaced above the path that was resolved before
CHECK(doc.patch(json::parse(R"([ const json replaced_above = json::parse(R"([
{"op": "test", "path": "/c/d/e", "value": 7}, {"op": "test", "path": "/c/d/e", "value": 7},
{"op": "replace", "path": "/c/d", "value": {"e": 8, "f": {"g": 9}}}, {"op": "replace", "path": "/c/d", "value": {"e": 8, "f": {"g": 9}}},
{"op": "test", "path": "/c/d/e", "value": 8}, {"op": "test", "path": "/c/d/e", "value": 8},
@@ -2708,7 +2710,8 @@ TEST_CASE("JSON patch - consecutive operations with paths that share a prefix")
{"op": "remove", "path": "/c/d/f"}, {"op": "remove", "path": "/c/d/f"},
{"op": "add", "path": "/c/d/f", "value": [1]}, {"op": "add", "path": "/c/d/f", "value": [1]},
{"op": "test", "path": "/c/d/f/0", "value": 1} {"op": "test", "path": "/c/d/f/0", "value": 1}
])")) == json::parse(R"({"a": [{"b": [1, 2, 3]}, {"b": [4, 5, 6]}], "c": {"d": {"e": 8, "f": [1]}}})")); ])");
CHECK(doc.patch(replaced_above) == json::parse(R"({"a": [{"b": [1, 2, 3]}, {"b": [4, 5, 6]}], "c": {"d": {"e": 8, "f": [1]}}})"));
#if !defined(JSON_NOEXCEPTION) #if !defined(JSON_NOEXCEPTION)
// a value removed above the path that was resolved before is gone // a value removed above the path that was resolved before is gone
+4 -3
View File
@@ -916,14 +916,15 @@ TEST_CASE("json_view document_data::holds() (the debug check of read())")
SECTION("owned text") SECTION("owned text")
{ {
d->owned = "{\"a\":\"some text that does not fit a short string\"}"; d->owned = R"({"a":"some text that does not fit a short string"})";
const char* const b = d->owned.data(); const char* const b = d->owned.data();
CHECK(d->holds(b, 1)); CHECK(d->holds(b, 1));
CHECK(d->holds(b + 5, 3)); CHECK(d->holds(b + 5, 3));
CHECK(d->holds(b + d->owned.size() - 1, 1)); CHECK(d->holds(b + d->owned.size() - 1, 1));
// also after clear(), which keeps the buffer that the next parse writes // also after clear(), which keeps the buffer that the next parse writes
d->owned.clear(); d->owned.clear();
CHECK(d->holds(b, 4)); // (b is only compared with the buffer, never read)
CHECK(d->holds(b, 4)); // NOLINT(clang-analyzer-cplusplus.InnerPointer)
CHECK(!d->holds(other.data(), other.size())); CHECK(!d->holds(other.data(), other.size()));
CHECK(!d->holds(b, 0)); CHECK(!d->holds(b, 0));
} }
@@ -946,7 +947,7 @@ TEST_CASE("json_view document_data::holds() (the debug check of read())")
{ {
d->edits.reset(new document_data::edit_state()); // NOLINT(cppcoreguidelines-owning-memory) d->edits.reset(new document_data::edit_state()); // NOLINT(cppcoreguidelines-owning-memory)
CHECK(!d->holds(other.data(), other.size())); CHECK(!d->holds(other.data(), other.size()));
d->edits->texts.emplace_back(new char[16]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) d->edits->texts.emplace_back(new char[16]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-owning-memory)
d->edits->text_cap = 16; d->edits->text_cap = 16;
CHECK(d->holds(d->edits->texts.back().get() + 3, 3)); CHECK(d->holds(d->edits->texts.back().get() + 3, 3));
CHECK(!d->holds(other.data(), other.size())); CHECK(!d->holds(other.data(), other.size()));
+1 -1
View File
@@ -494,7 +494,7 @@ TEST_CASE("walks over values nested around the recursion depth limit")
for_each_value([](const std::string & kinds) for_each_value([](const std::string & kinds)
{ {
const json a = build(kinds, 1); const json a = build(kinds, 1);
const std::hash<json> hash; const std::hash<json> hash{};
CHECK(hash(a) == hash(json(a))); CHECK(hash(a) == hash(json(a)));
CHECK(hash(a) == hash(json::parse(expected_compact(kinds, "1")))); CHECK(hash(a) == hash(json::parse(expected_compact(kinds, "1"))));
CHECK(hash(a) != hash(build(kinds, 2))); CHECK(hash(a) != hash(build(kinds, 2)));