Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .inferconfig
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 2 additions & 0 deletions include/nlohmann/detail/output/binary_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
3 changes: 3 additions & 0 deletions include/nlohmann/ordered_map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,

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
}
Expand Down
5 changes: 5 additions & 0 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -27136,6 +27138,9 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,

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
}
Expand Down
7 changes: 5 additions & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that was fixed now.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is fixed. The reordering in #5585 removed the false positive, so the comment was out of date. The test is now the regression test that keeps it fixed, and #5784 rewords the comment to say that.

Posted by Claude Code on behalf of @nlohmann.

# -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>
)

#############################################################################
Expand Down
8 changes: 7 additions & 1 deletion tests/src/unit-diagnostics-optimized.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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
14 changes: 7 additions & 7 deletions tests/src/unit-regression2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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})}})
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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());
Expand Down
12 changes: 12 additions & 0 deletions tests/src/unit-regression3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
11 changes: 9 additions & 2 deletions tests/src/unit-serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading