From 7cb4d3284e382b30a4de2a44b1b1b79a06df63b0 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 6 Sep 2026 12:18:38 +0200 Subject: [PATCH] Fix discarded nodiscard results across the test suite for GCC's warn_unused_result A plain (void) cast on a call expression suppresses the C++17 [[nodiscard]] warning but not GCC's warning for functions annotated via the GNU __attribute__((warn_unused_result)) form -- which is what JSON_HEDLEY_WARN_UNUSED_RESULT expands to on GCC. Several existing tests that call a newly-annotated function (dump(), empty()) purely to check that it throws/does not throw, discarding the result via (void), newly warned (and failed -Werror builds) once the annotation was broadened. Route those discards through a small ignore_return_value() helper instead, which actually consumes the value and suppresses the warning on both attribute forms. Signed-off-by: Niels Lohmann --- tests/src/test_utils.hpp | 9 ++++++++ tests/src/unit-class_parser.cpp | 5 ++++- ...unit-class_parser_diagnostic_positions.cpp | 5 ++++- tests/src/unit-regression1.cpp | 4 +++- tests/src/unit-regression2.cpp | 22 +++++++++++++------ tests/src/unit-serialization.cpp | 17 +++++++++----- tests/src/unit-unicode1.cpp | 7 ++++-- tests/src/unit-unicode2.cpp | 4 +++- tests/src/unit-unicode3.cpp | 4 +++- tests/src/unit-unicode4.cpp | 4 +++- tests/src/unit-unicode5.cpp | 4 +++- 11 files changed, 63 insertions(+), 22 deletions(-) diff --git a/tests/src/test_utils.hpp b/tests/src/test_utils.hpp index baa802f71..4c81a8ef4 100644 --- a/tests/src/test_utils.hpp +++ b/tests/src/test_utils.hpp @@ -15,6 +15,15 @@ namespace utils { +// Some tests intentionally discard the [[nodiscard]]/JSON_HEDLEY_WARN_UNUSED_RESULT +// return value of a call they only make to exercise its side effects (e.g. checking +// that it does not throw). A plain (void) cast on the call expression does not +// suppress GCC's warning for functions using the GNU __attribute__((warn_unused_result)) +// form (as opposed to the C++17 [[nodiscard]] attribute) -- passing the value into an +// ordinary function call does. +template +inline void ignore_return_value(T&& /*unused*/) noexcept {} + inline std::vector read_binary_file(const std::string& filename) { std::ifstream file(filename, std::ios::binary); diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 8b3ea660e..b6c6bff6a 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -23,6 +23,8 @@ using nlohmann::json; #include #include +#include "test_utils.hpp" + namespace { class SaxEventLogger @@ -624,7 +626,8 @@ TEST_CASE("parser class") SECTION("overflow") { // overflows during parsing yield an exception - CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&); + // empty() is nodiscard; the exception is thrown by parser_helper() itself, before empty() would run + CHECK_THROWS_WITH_AS(utils::ignore_return_value(parser_helper("1.18973e+4932").empty()), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&); } SECTION("invalid numbers") diff --git a/tests/src/unit-class_parser_diagnostic_positions.cpp b/tests/src/unit-class_parser_diagnostic_positions.cpp index 2697ecf8a..794182f30 100644 --- a/tests/src/unit-class_parser_diagnostic_positions.cpp +++ b/tests/src/unit-class_parser_diagnostic_positions.cpp @@ -22,6 +22,8 @@ using nlohmann::json; #include +#include "test_utils.hpp" + namespace { class SaxEventLogger @@ -629,7 +631,8 @@ TEST_CASE("parser class") SECTION("overflow") { // overflows during parsing yield an exception - CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&); + // empty() is nodiscard; the exception is thrown by parser_helper() itself, before empty() would run + CHECK_THROWS_WITH_AS(utils::ignore_return_value(parser_helper("1.18973e+4932").empty()), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&); } SECTION("invalid numbers") diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp index 475ef511f..fdc3fdd05 100644 --- a/tests/src/unit-regression1.cpp +++ b/tests/src/unit-regression1.cpp @@ -29,6 +29,7 @@ using nlohmann::json; #include #include #include "make_test_data_available.hpp" +#include "test_utils.hpp" #ifdef JSON_HAS_CPP_17 #include @@ -1373,7 +1374,8 @@ TEST_CASE("regression tests 1") std::array key1 = {{ 103, 92, 117, 48, 48, 48, 55, 92, 114, 215, 126, 214, 95, 92, 34, 174, 40, 71, 38, 174, 40, 71, 38, 223, 134, 247, 127, 0 }}; std::string const key1_str(reinterpret_cast(key1.data())); json const j = key1_str; - CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&); + // dump() is nodiscard; the exception is thrown by dump() itself before it would return + CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&); } #if JSON_USE_IMPLICIT_CONVERSIONS diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 8f2397c68..2d904707e 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -31,6 +31,8 @@ using ordered_json = nlohmann::ordered_json; #include #include +#include "test_utils.hpp" + #ifdef JSON_HAS_CPP_17 #include #include @@ -639,8 +641,12 @@ TEST_CASE("regression tests 2") s += static_cast(i); } dump_test["1"] = s; - // dump() is nodiscard; this only checks that dumping does not throw/crash - (void)dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace); + // dump() is nodiscard; this only checks that dumping does not throw/crash. + // A (void) cast on the call itself does not suppress GCC's warning for the + // GNU warn_unused_result attribute (unlike a real C++17 [[nodiscard]]), so + // capture the result in a variable and discard that instead. + auto dump_result = dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace); + (void)dump_result; } } @@ -732,12 +738,14 @@ TEST_CASE("regression tests 2") { const std::array data = {{0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}}; const json j = json::from_msgpack(data.data(), data.size()); + // dump() is nodiscard; this only checks that dumping does not throw CHECK_NOTHROW( - j.dump(4, // Indent - ' ', // Indent char - false, // Ensure ascii - json::error_handler_t::strict // Error - )); + utils::ignore_return_value( + j.dump(4, // Indent + ' ', // Indent char + false, // Ensure ascii + json::error_handler_t::strict // Error + ))); } SECTION("PR #2181 - regression bug with lvalue") diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index f55ed8470..caf720671 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp @@ -15,6 +15,8 @@ using nlohmann::json; #include #include +#include "test_utils.hpp" + TEST_CASE("serialization") { SECTION("operator<<") @@ -84,8 +86,9 @@ TEST_CASE("serialization") { const json j = "ä\xA9ü"; - CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&); - CHECK_THROWS_WITH_AS(j.dump(1, ' ', false, json::error_handler_t::strict), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&); + // dump() is nodiscard; the exception is thrown by dump() itself before it would return + CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&); + CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&); CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"äü\""); CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"ä\xEF\xBF\xBDü\""); CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\""); @@ -95,8 +98,9 @@ TEST_CASE("serialization") { const json j = "123\xC2"; - CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&); - CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&); + // dump() is nodiscard; the exception is thrown by dump() itself before it would return + CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&); + CHECK_THROWS_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), json::type_error&); CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123\""); CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\""); CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd\""); @@ -106,8 +110,9 @@ TEST_CASE("serialization") { const json j = "123\xF1\xB0\x34\x35\x36"; - CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0x34", json::type_error&); - CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&); + // dump() is nodiscard; the exception is thrown by dump() itself before it would return + CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0x34", json::type_error&); + CHECK_THROWS_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), json::type_error&); CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123456\""); CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\x34\x35\x36\""); CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd456\""); diff --git a/tests/src/unit-unicode1.cpp b/tests/src/unit-unicode1.cpp index 174ce1395..2d744003a 100644 --- a/tests/src/unit-unicode1.cpp +++ b/tests/src/unit-unicode1.cpp @@ -17,6 +17,7 @@ using nlohmann::json; #include #include #include "make_test_data_available.hpp" +#include "test_utils.hpp" TEST_CASE("Unicode (1/5)" * doctest::skip()) { @@ -240,7 +241,8 @@ void roundtrip(bool success_expected, const std::string& s) if (success_expected) { // serialization succeeds - CHECK_NOTHROW(j.dump()); + // dump() is nodiscard; this only checks that dumping does not throw + CHECK_NOTHROW(utils::ignore_return_value(j.dump())); // exclude parse test for U+0000 if (s[0] != '\0') @@ -259,7 +261,8 @@ void roundtrip(bool success_expected, const std::string& s) else { // serialization fails - CHECK_THROWS_AS(j.dump(), json::type_error&); + // dump() is nodiscard; the exception is thrown by dump() itself before it would return + CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&); // parsing JSON text fails CHECK_THROWS_AS(_ = json::parse(ps), json::parse_error&); diff --git a/tests/src/unit-unicode2.cpp b/tests/src/unit-unicode2.cpp index fb68815ba..a9649b4de 100644 --- a/tests/src/unit-unicode2.cpp +++ b/tests/src/unit-unicode2.cpp @@ -19,6 +19,7 @@ using nlohmann::json; #include #include #include "make_test_data_available.hpp" +#include "test_utils.hpp" // this test suite uses static variables with non-trivial destructors DOCTEST_CLANG_SUPPRESS_WARNING_PUSH @@ -97,7 +98,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 else { // strict mode must throw if success is not expected - CHECK_THROWS_AS(j.dump(), json::type_error&); + // dump() is nodiscard; the exception is thrown by dump() itself before it would return + CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&); // ignore and replace must create different dumps CHECK(s_ignored != s_replaced); diff --git a/tests/src/unit-unicode3.cpp b/tests/src/unit-unicode3.cpp index 739a3dad3..d5627d8cc 100644 --- a/tests/src/unit-unicode3.cpp +++ b/tests/src/unit-unicode3.cpp @@ -19,6 +19,7 @@ using nlohmann::json; #include #include #include "make_test_data_available.hpp" +#include "test_utils.hpp" // this test suite uses static variables with non-trivial destructors DOCTEST_CLANG_SUPPRESS_WARNING_PUSH @@ -97,7 +98,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 else { // strict mode must throw if success is not expected - CHECK_THROWS_AS(j.dump(), json::type_error&); + // dump() is nodiscard; the exception is thrown by dump() itself before it would return + CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&); // ignore and replace must create different dumps CHECK(s_ignored != s_replaced); diff --git a/tests/src/unit-unicode4.cpp b/tests/src/unit-unicode4.cpp index f7047201c..f15a1499f 100644 --- a/tests/src/unit-unicode4.cpp +++ b/tests/src/unit-unicode4.cpp @@ -19,6 +19,7 @@ using nlohmann::json; #include #include #include "make_test_data_available.hpp" +#include "test_utils.hpp" // this test suite uses static variables with non-trivial destructors DOCTEST_CLANG_SUPPRESS_WARNING_PUSH @@ -97,7 +98,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 else { // strict mode must throw if success is not expected - CHECK_THROWS_AS(j.dump(), json::type_error&); + // dump() is nodiscard; the exception is thrown by dump() itself before it would return + CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&); // ignore and replace must create different dumps CHECK(s_ignored != s_replaced); diff --git a/tests/src/unit-unicode5.cpp b/tests/src/unit-unicode5.cpp index e4dcc2131..e35801823 100644 --- a/tests/src/unit-unicode5.cpp +++ b/tests/src/unit-unicode5.cpp @@ -19,6 +19,7 @@ using nlohmann::json; #include #include #include "make_test_data_available.hpp" +#include "test_utils.hpp" // this test suite uses static variables with non-trivial destructors DOCTEST_CLANG_SUPPRESS_WARNING_PUSH @@ -97,7 +98,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 else { // strict mode must throw if success is not expected - CHECK_THROWS_AS(j.dump(), json::type_error&); + // dump() is nodiscard; the exception is thrown by dump() itself before it would return + CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&); // ignore and replace must create different dumps CHECK(s_ignored != s_replaced);