Fix CI on develop after #5585 (#5779)

* Fix CI on develop after #5585

- test-diagnostics-optimized: -O3 makes GCC's -Winline and
  -Wsuggest-attribute=pure/const warnings fire with the ci_test_gcc flag
  set; turn them off for this test.
- test-diagnostics-optimized: suppress Clang's -Wexit-time-destructors for
  the static table in to_json.
- Infer: raise pulse-max-disjuncts from 20 to 40. With the default,
  Pulse loses the stored type in basic_json::replace_value() and reports
  false null dereferences of get_ptr() results in unit-pointer_access.cpp.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Ignore Infer's false USE_AFTER_DELETE in ordered_map::erase

Infer's std::string model keeps the buffer of a moved-from string, so the
destroy-and-reconstruct loop in erase(first, last) looks like it destroys a
buffer twice.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Mark throw_on_discarded()'s parameters as used without exceptions

With JSON_NOEXCEPTION, JSON_THROW expands to std::abort(), so Clang's
-Wunused-parameter breaks test-disabled_exceptions (since #5761).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Skip the span_input_adapter sax_parse checks with deleted deprecated functions

The #5676 regression test (#5740) calls the deprecated
sax_parse(span_input_adapter&&, ...), which JSON_DELETE_DEPRECATED_FUNCTIONS
deletes, so ci_test_delete_deprecated_functions failed to build.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fall back to the first entry in test-diagnostics-optimized's to_json

clang-tidy (clang-analyzer-security.ArrayBound) flagged it->second for a
value not in the table. Use the same fallback as
NLOHMANN_JSON_SERIALIZE_ENUM; the test still fails with -Werror=array-bounds
on the headers from before #5585.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy findings in tests from #5762 and #5774

- unit-regression2.cpp (#5762): const/auto for the destroy() test values;
  NOLINT the intended copy in check_destroy_edge_case().
- unit-serialization.cpp (#5774): build the expected strings with += instead
  of chained operator+ (performance-inefficient-string-concatenation).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann authored and GitHub committed 2026-10-07 16:37:19 +02:00
1 parent 30e542e52a
commit 367336c83d
9 files changed
+52 -12

No files matched your search

+2
View File
@@ -1,5 +1,7 @@
{
"_comment": "Used by the ci_infer CMake target (#5715 item 4b). fail-on-issue makes CI fail on Infer findings; disable-issue-type is a type-level baseline for the ~174 pre-existing findings (all PULSE_UNNECESSARY_COPY*/PULSE_RESOURCE_LEAK/PULSE_CONST_REFABLE, mostly in test code) triaged in run https://github.com/nlohmann/json/actions/runs/35829411620 on commit 1054b2097, so CI fails only on a NEW issue type. Remove an entry here once its findings have been fixed or explicitly accepted.",
"_comment_pulse": "Pulse stops exploring paths after pulse-max-disjuncts (default 20). With the default, basic_json::replace_value() (destroy + assert_invariant) exceeds the limit, Pulse loses the stored type, and reports false NULLPTR_DEREFERENCE findings for get_ptr() results in tests/src/unit-pointer_access.cpp.",
"pulse-max-disjuncts": 40,
"fail-on-issue": true,
"disable-issue-type": [
"PULSE_UNNECESSARY_COPY_ASSIGNMENT",
@@ -931,6 +931,8 @@ class binary_writer
*/
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
{
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(format_name);
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
}
+3
View File
@@ -259,6 +259,9 @@ public:
for (auto it = first; std::next(it, elements_affected) != Container::end(); ++it)
{
// false positive: Infer's model of std::string keeps the buffer of a
// moved-from string, so it assumes a buffer is destroyed twice
// @infer-ignore USE_AFTER_DELETE
it->~value_type(); // destroy but keep allocation
new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // "move" next element to it
}
+5
View File
@@ -22402,6 +22402,8 @@ class binary_writer
*/
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
{
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(format_name);
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
}
@@ -27136,6 +27138,9 @@ public:
for (auto it = first; std::next(it, elements_affected) != Container::end(); ++it)
{
// false positive: Infer's model of std::string keeps the buffer of a
// moved-from string, so it assumes a buffer is destroyed twice
// @infer-ignore USE_AFTER_DELETE
it->~value_type(); // destroy but keep allocation
new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // "move" next element to it
}
+5 -2
View File
@@ -138,9 +138,12 @@ json_test_set_test_options(test-disabled_exceptions
# only the #972 regression test needs thirdparty/fifo_map on its include path
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
# GCC's false -Warray-bounds error with JSON_DIAGNOSTICS only shows up when optimizing (#5742)
# GCC's false -Warray-bounds error with JSON_DIAGNOSTICS only shows up when optimizing (#5742).
# -O3 makes the optimizer-driven warnings of the ci_test_gcc flag set (-Winline,
# -Wsuggest-attribute=...) fire on the library's inline functions; they are not
# what this test checks, so turn them off for it.
json_test_set_test_options(test-diagnostics-optimized
COMPILE_OPTIONS $<$<CXX_COMPILER_ID:GNU>:-O3 -Werror=array-bounds>
COMPILE_OPTIONS $<$<CXX_COMPILER_ID:GNU>:-O3 -Werror=array-bounds -Wno-inline -Wno-suggest-attribute=pure -Wno-suggest-attribute=const>
)
#############################################################################
+7 -1
View File
@@ -34,6 +34,10 @@ using nlohmann::json;
#include <utility>
#include <vector>
// the static table in to_json has an exit-time destructor
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
namespace
{
enum class diag_color
@@ -55,7 +59,7 @@ void to_json(json& j, const diag_color& c)
{
return p.first == c;
});
j = it->second;
j = ((it != std::end(m)) ? it : std::begin(m))->second; // like NLOHMANN_JSON_SERIALIZE_ENUM
}
} // namespace
@@ -78,3 +82,5 @@ TEST_CASE("diagnostics with optimization")
CHECK_THROWS_WITH_AS(j[1].get<int>(), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error);
}
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP
+7 -7
View File
@@ -1035,7 +1035,7 @@ TEST_CASE("regression test #5135 - destructor never allocates, even under memory
failing_allocator_allocations = 0;
failing_allocator_deallocations = 0;
{
failing_json j = failing_json::array(
const failing_json j = failing_json::array(
{
failing_json::array({1, 2}),
failing_json::object({{"key", failing_json::array({3})}})
@@ -1052,7 +1052,7 @@ TEST_CASE("regression test #5135 - destructor never allocates, even under memory
{
std::size_t allocations_before = 0;
{
failing_json j = make_deep_nest<failing_json>(100000, false);
const auto j = make_deep_nest<failing_json>(100000, false);
allocations_before = failing_allocator_allocations;
fail_next_allocation = true;
}
@@ -1066,7 +1066,7 @@ TEST_CASE("regression test #5135 - destructor never allocates, even under memory
{
std::size_t allocations_before = 0;
{
failing_json j = make_deep_nest<failing_json>(100000, true);
const auto j = make_deep_nest<failing_json>(100000, true);
allocations_before = failing_allocator_allocations;
fail_next_allocation = true;
}
@@ -1080,7 +1080,7 @@ TEST_CASE("regression test #5135 - destructor never allocates, even under memory
{
std::size_t allocations_before = 0;
{
failing_ordered_json j = make_deep_nest<failing_ordered_json>(100000, true);
const auto j = make_deep_nest<failing_ordered_json>(100000, true);
allocations_before = failing_allocator_allocations;
fail_next_allocation = true;
}
@@ -1138,7 +1138,7 @@ BasicJsonType make_single_chain(std::size_t depth)
template<class BasicJsonType>
void check_destroy_edge_case(const BasicJsonType& value)
{
const BasicJsonType copy = value;
const BasicJsonType copy = value; // NOLINT(performance-unnecessary-copy-initialization): the copy is the point
CHECK(copy == value);
}
} // namespace
@@ -1183,7 +1183,7 @@ TEST_CASE_TEMPLATE("regression test #5135 - destroy() edge cases", BasicJsonType
SECTION("single-element chain, 1000 levels deep")
{
BasicJsonType root = make_single_chain<BasicJsonType>(1000);
auto root = make_single_chain<BasicJsonType>(1000);
check_destroy_edge_case(root);
}
@@ -1220,7 +1220,7 @@ TEST_CASE_TEMPLATE("regression test #5135 - destroy() edge cases", BasicJsonType
SECTION("destruction via assignment on a deep tree")
{
BasicJsonType root = make_single_chain<BasicJsonType>(2000);
auto root = make_single_chain<BasicJsonType>(2000);
// assigning a new value destroys the old one in place
root = nullptr;
CHECK(root.is_null());
+12
View File
@@ -22,6 +22,14 @@
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
// from a plain JSON array, not just from an already-binary value) relies on
// enum serialization being enabled
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
#endif
@@ -887,19 +895,23 @@ TEST_CASE("issue #5676 - SAX parsing of CBOR tags")
true, false, false, json::cbor_tag_handler_t::store));
CHECK(iterator_parsed == expected);
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
json span_parsed;
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> span_sax(span_parsed);
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(cbor.data(), cbor.size()), &span_sax,
json::input_format_t::cbor, true, false, false, json::cbor_tag_handler_t::store));
CHECK(span_parsed == expected);
#endif
const std::string text = "null";
CHECK(json::sax_parse(text, &acceptor, json::input_format_t::json,
true, false, false, json::cbor_tag_handler_t::store));
CHECK(json::sax_parse(text.begin(), text.end(), &acceptor, json::input_format_t::json,
true, false, false, json::cbor_tag_handler_t::store));
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(text.data(), text.size()), &acceptor,
json::input_format_t::json, true, false, false, json::cbor_tag_handler_t::store));
#endif
}
TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with an object")
+9 -2
View File
@@ -945,8 +945,15 @@ TEST_CASE("serialization boundary values for the string buffer")
// newline escaping does not depend on ensure_ascii: only the
// emoji differs (raw UTF-8 bytes vs. a \u-escaped surrogate pair)
CHECK(j.dump(-1, ' ', false) == '"' + expected_prefix + emoji + '"');
CHECK(j.dump(-1, ' ', true) == '"' + expected_prefix + "\\ud83d\\ude00\"");
std::string expected_raw = "\"";
expected_raw += expected_prefix;
expected_raw += emoji;
expected_raw += '"';
std::string expected_ascii = "\"";
expected_ascii += expected_prefix;
expected_ascii += R"(\ud83d\ude00")";
CHECK(j.dump(-1, ' ', false) == expected_raw);
CHECK(j.dump(-1, ' ', true) == expected_ascii);
CHECK(json::parse(j.dump(-1, ' ', true)) == j);
CHECK(json::parse(j.dump(-1, ' ', false)) == j);
}