diff --git a/.clang-tidy b/.clang-tidy index 7132199dd..fa3e03ae3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -84,6 +84,10 @@ Checks: '*, CheckOptions: - key: hicpp-special-member-functions.AllowSoleDefaultDtor value: 1 + # clang-tidy 22.1 extended this check to classes and enums; the test files + # define many such helper types at namespace scope, which is harmless + - key: misc-use-internal-linkage.AnalyzeTypes + value: false WarningsAsErrors: '*' diff --git a/cmake/ci.cmake b/cmake/ci.cmake index 353975b8e..c23f2679e 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -596,7 +596,12 @@ foreach(SRC_FILE ${SRC_FILES}) add_executable(single_${RELATIVE_SRC_FILE} EXCLUDE_FROM_ALL ${PROJECT_BINARY_DIR}/src_single/${RELATIVE_SRC_FILE}.cpp) target_include_directories(single_${RELATIVE_SRC_FILE} PRIVATE ${PROJECT_SOURCE_DIR}/include) target_compile_features(single_${RELATIVE_SRC_FILE} PRIVATE cxx_std_11) - set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") + if(RELATIVE_SRC_FILE STREQUAL "json") + # see below: report json.hpp's diagnostics without --error, so they do not fail the build + set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${IWYU_TOOL} -Xiwyu --max_line_length=300) + else() + set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") + endif() # remember binary for ci_single_binaries list(APPEND single_binaries single_${RELATIVE_SRC_FILE}) # json.hpp pulls together the whole library behind heavily templated, SFINAE-based code, and @@ -658,14 +663,19 @@ function(ci_get_cmake version var) OUTPUT ${${var}} COMMAND wget -nc https://github.com/Kitware/CMake/releases/download/v${version}/cmake-${version}-linux-x86_64.tar.gz COMMAND wget -nc https://github.com/Kitware/CMake/releases/download/v${version}/cmake-${version}-SHA-256.txt - # verify the archive against Kitware's published SHA-256 sums before unpacking it - COMMAND sh -c "grep ' cmake-${version}-linux-x86_64[.]tar[.]gz$' cmake-${version}-SHA-256.txt | sha256sum -c -" - COMMAND tar xfz cmake-${version}-linux-x86_64.tar.gz - COMMAND rm cmake-${version}-linux-x86_64.tar.gz cmake-${version}-SHA-256.txt + # verify the archive against Kitware's published SHA-256 sums before unpacking it; old + # releases list the archive as "Linux-x86_64", so match case-insensitively and rewrite + # the name to the lowercase one the download was saved under + COMMAND sh -c "grep -i ' cmake-${version}-linux-x86_64[.]tar[.]gz$' cmake-${version}-SHA-256.txt | tr L l | sha256sum -c -" + # unpack into cmake-${version} directly, as the archive's top-level directory is spelled + # "Linux" in old releases and "linux" in newer ones COMMAND ${CMAKE_COMMAND} -E rm -rf cmake-${version} - COMMAND ${CMAKE_COMMAND} -E rename cmake-${version}-linux-x86_64 cmake-${version} + COMMAND ${CMAKE_COMMAND} -E make_directory cmake-${version} + COMMAND tar xfz cmake-${version}-linux-x86_64.tar.gz -C cmake-${version} --strip-components=1 + COMMAND rm cmake-${version}-linux-x86_64.tar.gz cmake-${version}-SHA-256.txt WORKING_DIRECTORY ${PROJECT_BINARY_DIR} COMMENT "Download prebuilt CMake ${version}" + VERBATIM ) else() # no prebuilt archive for this platform (e.g. macOS or Linux aarch64): build from source diff --git a/cmake/gcc_flags.cmake b/cmake/gcc_flags.cmake index 0a3272c7a..d1081ae0e 100644 --- a/cmake/gcc_flags.cmake +++ b/cmake/gcc_flags.cmake @@ -164,7 +164,6 @@ set(GCC_CXXFLAGS -Wenum-conversion -Wexceptions -Wexpansion-to-defined - -Wexperimental-fmv-target -Wexpose-global-module-tu-local -Wexternal-tu-local -Wextra diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index df5c88523..49b3c32e2 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -235,8 +235,11 @@ struct external_constructor for (auto&& x : std::forward(arr)) { j.m_data.m_value.array->push_back(x); - j.set_parent(j.m_data.m_value.array->back()); } + // set the parents only once all elements are in place: a push_back + // that reallocates moves the earlier elements, which does not keep + // their parent pointers + j.set_parents(); j.assert_invariant(); } #endif diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 18511071f..86f00bef6 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -614,13 +614,13 @@ class binary_reader case 0x10: // int32 { std::int32_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); + return get_number(input_format_t::bson, value) && sax->number_integer(conditional_static_cast(value)); } case 0x12: // int64 { std::int64_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); + return get_number(input_format_t::bson, value) && sax->number_integer(conditional_static_cast(value)); } case 0x11: // uint64 @@ -659,7 +659,7 @@ class binary_reader parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "negative integer overflow", "value"), nullptr)); } - return sax->number_integer(static_cast(-1) - static_cast(number)); + return sax->number_integer(conditional_static_cast(static_cast(-1) - static_cast(number))); } /*! @@ -1905,25 +1905,25 @@ class binary_reader case 0xD0: // int 8 { std::int8_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD1: // int 16 { std::int16_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD2: // int 32 { std::int32_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD3: // int 64 { std::int64_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xDC: // array 16 @@ -2980,25 +2980,25 @@ class binary_reader case 'i': { std::int8_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'I': { std::int16_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'l': { std::int32_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'L': { std::int64_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'u': @@ -3558,7 +3558,7 @@ class binary_reader // integer -1..-10 if (byte <= 0xC1) { - return sax->number_integer(-1 - static_cast(byte - 0xB8)); + return sax->number_integer(conditional_static_cast(-1 - static_cast(byte - 0xB8))); } // 0xC2..0xF7: a UTF-8 lead byte begins a string if a continuation @@ -3877,6 +3877,8 @@ class binary_reader template bool get_to(T& dest, const input_format_t format, const char* context) { + // false positive: new_chars_read is read on the next lines + // @infer-ignore DEAD_STORE auto new_chars_read = ia.get_elements(&dest); chars_read += new_chars_read; if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T))) @@ -4128,6 +4130,8 @@ class binary_reader // resize() is required to make size() exactly old_size + wanted; // that is the room get_elements() is allowed to write into JSON_ASSERT(result.size() == old_size + wanted); + // false positive: bytes_read is read on the next lines + // @infer-ignore DEAD_STORE const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted); chars_read += bytes_read; if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted)) diff --git a/include/nlohmann/detail/input/number_parse.hpp b/include/nlohmann/detail/input/number_parse.hpp index b85e758cb..8971f7193 100644 --- a/include/nlohmann/detail/input/number_parse.hpp +++ b/include/nlohmann/detail/input/number_parse.hpp @@ -428,14 +428,14 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double } std::uint64_t w = 0; - int digits = 0; // significant digits in w + unsigned int digits = 0; // significant digits in w std::int64_t exponent = 0; bool truncated = false; bool in_fraction = false; for (;;) { // eight digits at a time, as long as they fit into w - while (w != 0 && digits <= 19 - 8 && last - p >= 8) + while (w != 0 && digits <= 19u - 8u && last - p >= 8) { const std::uint64_t v = read_eight_bytes(p); if (!is_eight_digits(v)) @@ -443,7 +443,7 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double break; } w = (w * 100000000u) + parse_eight_digits(v); - digits += 8; + digits += 8u; exponent -= in_fraction ? 8 : 0; p += 8; } @@ -459,7 +459,7 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double // leading zeros are not significant, but scale a fraction exponent -= in_fraction ? 1 : 0; } - else if (digits < 19) + else if (digits < 19u) { w = (w * 10u) + static_cast(c - '0'); ++digits; diff --git a/include/nlohmann/detail/string_utils.hpp b/include/nlohmann/detail/string_utils.hpp index 2495c5802..7c40f7395 100644 --- a/include/nlohmann/detail/string_utils.hpp +++ b/include/nlohmann/detail/string_utils.hpp @@ -57,7 +57,7 @@ inline std::string hex_byte(const std::uint8_t byte) Used to turn a decoded code point back into bytes: by the wide-string input adapters in input_adapters.hpp (one code point per UTF-32 unit, per UTF-16 unit outside the surrogate range, and per valid UTF-16 surrogate pair), and -by the lexer's `\uXXXX`/`\uXXXX\uYYYY` handling in lexer.hpp. Passing a +by the lexer's handling of u-escapes and surrogate pairs in lexer.hpp. Passing a code point above U+10FFFF, or one in the surrogate range U+D800..U+DFFF, is undefined behavior; callers are expected to have rejected those already (the wide-string adapters pass malformed units through unencoded instead of @@ -70,7 +70,7 @@ reaching it). @param[in] out called once for each byte of the UTF-8 encoding of @a cp */ template -void encode_utf8(std::uint32_t cp, Out&& out) +void encode_utf8(std::uint32_t cp, const Out& out) { JSON_ASSERT(cp <= 0x10FFFF); diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 91efcf27c..bbe310d9b 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -6524,6 +6524,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // Any other object type places its members itself - std::map // in key order, a hash map in an order its operator== ignores - // so a member-by-member diff always reproduces target there. +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(push ) +#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr +#endif if (!detail::is_ordered_map::value || (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)) { @@ -6534,6 +6538,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec common_keys = std::move(common_keys_source_order); return true; } +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning( pop ) +#endif // slow path: the common keys are in a different relative // order in source and target (only possible for a @@ -7030,10 +7037,10 @@ struct formatter // NOLINT(cert-dcl58-c int indent = -1; char indent_char = ' '; - constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator + constexpr format_parse_context::iterator parse(format_parse_context& ctx) { - auto it = ctx.begin(); - const auto end = ctx.end(); + format_parse_context::iterator it = ctx.begin(); + const format_parse_context::iterator end = ctx.end(); constexpr auto is_align = [](char c) { return c == '<' || c == '>' || c == '^'; diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index 7b6f63781..656f24264 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -12,12 +12,13 @@ #include // equal_to, less #include // initializer_list #include // input_iterator_tag, iterator_traits +#include // allocator #include // for operator new (placement new) #include // for out_of_range #include // forward_as_tuple #include // enable_if, integral_constant, is_convertible, is_nothrow_move_constructible #include // forward, move, pair, piecewise_construct -#include // vector, allocator +#include // vector #include #include diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e093e4ece..73079da57 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6261,7 +6261,7 @@ inline std::string hex_byte(const std::uint8_t byte) Used to turn a decoded code point back into bytes: by the wide-string input adapters in input_adapters.hpp (one code point per UTF-32 unit, per UTF-16 unit outside the surrogate range, and per valid UTF-16 surrogate pair), and -by the lexer's `\uXXXX`/`\uXXXX\uYYYY` handling in lexer.hpp. Passing a +by the lexer's handling of u-escapes and surrogate pairs in lexer.hpp. Passing a code point above U+10FFFF, or one in the surrogate range U+D800..U+DFFF, is undefined behavior; callers are expected to have rejected those already (the wide-string adapters pass malformed units through unencoded instead of @@ -6274,7 +6274,7 @@ reaching it). @param[in] out called once for each byte of the UTF-8 encoding of @a cp */ template -void encode_utf8(std::uint32_t cp, Out&& out) +void encode_utf8(std::uint32_t cp, const Out& out) { JSON_ASSERT(cp <= 0x10FFFF); @@ -6848,8 +6848,11 @@ struct external_constructor for (auto&& x : std::forward(arr)) { j.m_data.m_value.array->push_back(x); - j.set_parent(j.m_data.m_value.array->back()); } + // set the parents only once all elements are in place: a push_back + // that reallocates moves the earlier elements, which does not keep + // their parent pointers + j.set_parents(); j.assert_invariant(); } #endif @@ -9464,14 +9467,14 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double } std::uint64_t w = 0; - int digits = 0; // significant digits in w + unsigned int digits = 0; // significant digits in w std::int64_t exponent = 0; bool truncated = false; bool in_fraction = false; for (;;) { // eight digits at a time, as long as they fit into w - while (w != 0 && digits <= 19 - 8 && last - p >= 8) + while (w != 0 && digits <= 19u - 8u && last - p >= 8) { const std::uint64_t v = read_eight_bytes(p); if (!is_eight_digits(v)) @@ -9479,7 +9482,7 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double break; } w = (w * 100000000u) + parse_eight_digits(v); - digits += 8; + digits += 8u; exponent -= in_fraction ? 8 : 0; p += 8; } @@ -9495,7 +9498,7 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double // leading zeros are not significant, but scale a fraction exponent -= in_fraction ? 1 : 0; } - else if (digits < 19) + else if (digits < 19u) { w = (w * 10u) + static_cast(c - '0'); ++digits; @@ -14127,13 +14130,13 @@ class binary_reader case 0x10: // int32 { std::int32_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); + return get_number(input_format_t::bson, value) && sax->number_integer(conditional_static_cast(value)); } case 0x12: // int64 { std::int64_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); + return get_number(input_format_t::bson, value) && sax->number_integer(conditional_static_cast(value)); } case 0x11: // uint64 @@ -14172,7 +14175,7 @@ class binary_reader parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "negative integer overflow", "value"), nullptr)); } - return sax->number_integer(static_cast(-1) - static_cast(number)); + return sax->number_integer(conditional_static_cast(static_cast(-1) - static_cast(number))); } /*! @@ -15418,25 +15421,25 @@ class binary_reader case 0xD0: // int 8 { std::int8_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD1: // int 16 { std::int16_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD2: // int 32 { std::int32_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD3: // int 64 { std::int64_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xDC: // array 16 @@ -16493,25 +16496,25 @@ class binary_reader case 'i': { std::int8_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'I': { std::int16_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'l': { std::int32_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'L': { std::int64_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'u': @@ -17071,7 +17074,7 @@ class binary_reader // integer -1..-10 if (byte <= 0xC1) { - return sax->number_integer(-1 - static_cast(byte - 0xB8)); + return sax->number_integer(conditional_static_cast(-1 - static_cast(byte - 0xB8))); } // 0xC2..0xF7: a UTF-8 lead byte begins a string if a continuation @@ -17390,6 +17393,8 @@ class binary_reader template bool get_to(T& dest, const input_format_t format, const char* context) { + // false positive: new_chars_read is read on the next lines + // @infer-ignore DEAD_STORE auto new_chars_read = ia.get_elements(&dest); chars_read += new_chars_read; if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T))) @@ -17641,6 +17646,8 @@ class binary_reader // resize() is required to make size() exactly old_size + wanted; // that is the room get_elements() is allowed to write into JSON_ASSERT(result.size() == old_size + wanted); + // false positive: bytes_read is read on the next lines + // @infer-ignore DEAD_STORE const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted); chars_read += bytes_read; if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted)) @@ -26321,12 +26328,13 @@ NLOHMANN_JSON_NAMESPACE_END #include // equal_to, less #include // initializer_list #include // input_iterator_tag, iterator_traits +#include // allocator #include // for operator new (placement new) #include // for out_of_range #include // forward_as_tuple #include // enable_if, integral_constant, is_convertible, is_nothrow_move_constructible #include // forward, move, pair, piecewise_construct -#include // vector, allocator +#include // vector // #include @@ -33213,6 +33221,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // Any other object type places its members itself - std::map // in key order, a hash map in an order its operator== ignores - // so a member-by-member diff always reproduces target there. +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(push ) +#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr +#endif if (!detail::is_ordered_map::value || (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)) { @@ -33223,6 +33235,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec common_keys = std::move(common_keys_source_order); return true; } +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning( pop ) +#endif // slow path: the common keys are in a different relative // order in source and target (only possible for a @@ -33719,10 +33734,10 @@ struct formatter // NOLINT(cert-dcl58-c int indent = -1; char indent_char = ' '; - constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator + constexpr format_parse_context::iterator parse(format_parse_context& ctx) { - auto it = ctx.begin(); - const auto end = ctx.end(); + format_parse_context::iterator it = ctx.begin(); + const format_parse_context::iterator end = ctx.end(); constexpr auto is_align = [](char c) { return c == '<' || c == '>' || c == '^'; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ef1300ff6..aac6fee5e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -89,7 +89,8 @@ target_compile_options(test_main PUBLIC # https://github.com/nlohmann/json/pull/3229 $<$:-diag-disable=2196> - $<$:-Wno-deprecated-declarations> + # several tests call deprecated functions on purpose to keep them covered + $<$>:-Wno-deprecated-declarations> $<$:-diag-disable=1786>) target_include_directories(test_main SYSTEM PUBLIC thirdparty/doctest) @@ -101,7 +102,7 @@ target_link_libraries(test_main PUBLIC ${NLOHMANN_JSON_TARGET_NAME}) # test-regression1 needs it on its include path (see json_test_set_test_options # below), rather than every test-* target via test_main. add_library(fifo_map_include INTERFACE) -target_include_directories(fifo_map_include INTERFACE thirdparty/fifo_map) +target_include_directories(fifo_map_include SYSTEM INTERFACE thirdparty/fifo_map) ############################################################################# # define test- and standard-specific build settings diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index f99abd2fc..fbdfaa351 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -372,6 +372,11 @@ void check_deep_copy_survives_failing_allocation(bool nest_objects) TEST_CASE("copy of a deeply nested value survives a failing allocation (#5640)") { + // With iterator debugging (MSVC STL debug builds, also used by clang-cl), + // containers allocate a debug proxy through the allocator inside their + // noexcept move constructors, so failing that allocation terminates the + // program instead of throwing std::bad_alloc. Nothing to check there. +#if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) SECTION("std::map-backed object_t") { using bad_alloc_json = nlohmann::basic_json(false); check_deep_copy_survives_failing_allocation(true); } +#endif } namespace @@ -493,9 +499,13 @@ struct countdown_allocator : std::allocator template void construct(U* p, Args&& ... args) { - if (constructions_until_failure != 0 && --constructions_until_failure == 0) + if (constructions_until_failure != 0) { - throw std::bad_alloc(); + --constructions_until_failure; + if (constructions_until_failure == 0) + { + throw std::bad_alloc(); + } } ::new (static_cast(p)) U(std::forward(args)...); diff --git a/tests/src/unit-alt-string.cpp b/tests/src/unit-alt-string.cpp index cac2d183d..8b86c78e8 100644 --- a/tests/src/unit-alt-string.cpp +++ b/tests/src/unit-alt-string.cpp @@ -16,6 +16,10 @@ #include #include +// NLOHMANN_JSON_SERIALIZE_ENUM_STRICT uses a static std::pair +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") + /* forward declarations */ class alt_string; bool operator<(const char* op1, const alt_string& op2) noexcept; // NOLINT(misc-use-internal-linkage) @@ -174,7 +178,7 @@ bool operator<(const char* op1, const alt_string& op2) noexcept return op1 < op2.str_impl; } -enum class alt_color { red, green }; +enum class alt_color { red, green }; // NOLINT(misc-use-internal-linkage) // NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(alt_color, @@ -434,3 +438,5 @@ TEST_CASE("alternative string type") CHECK_THROWS_WITH_AS(_ = doc.get(), "[json.exception.out_of_range.410] enum value out of range for alt_color: \"blue\"", alt_json::out_of_range&); } } + +DOCTEST_CLANG_SUPPRESS_WARNING_POP diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 8a0c0fc58..b6be66c9e 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -67,10 +67,9 @@ TEST_CASE("BJData") { SECTION("binary_reader BJData lookup tables") { + // both lookups are static member functions std::vector const data; - auto ia = nlohmann::detail::input_adapter(data); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - nlohmann::detail::binary_reader const br{std::move(ia), json::input_format_t::bjdata}; + using reader_t = nlohmann::detail::binary_reader; // the excluded optimized-type markers must match binary_writer's // is_bjdata_excluded_type_marker(), which encodes the same 8 markers @@ -78,13 +77,13 @@ TEST_CASE("BJData") {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z' }) { - CHECK(br.is_bjd_excluded_optimized_type(marker)); + CHECK(reader_t::is_bjd_excluded_optimized_type(static_cast(marker))); } for (const char marker : {'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'x' }) { - CHECK(!br.is_bjd_excluded_optimized_type(marker)); + CHECK(!reader_t::is_bjd_excluded_optimized_type(static_cast(marker))); } // every dtype marker must round-trip to its ND-array type name @@ -96,11 +95,11 @@ TEST_CASE("BJData") }; for (const auto& type : types) { - const char* name = br.bjd_type_name(type.first); + const char* name = reader_t::bjd_type_name(static_cast(type.first)); REQUIRE(name != nullptr); CHECK(std::string(name) == type.second); } - CHECK(br.bjd_type_name('x') == nullptr); + CHECK(reader_t::bjd_type_name(static_cast('x')) == nullptr); } SECTION("individual values") diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 9444c2a35..f67ba631b 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -2001,7 +2001,7 @@ TEST_CASE("parser class") const json j = json::parse(R"({"skip": {"k1": 1, "k2": [2, {"k3": 3}]}, "keep": 1})", [&](int depth, json::parse_event_t event, json & parsed) { - static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; + static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) log.push_back(std::to_string(depth) + " " + names[static_cast(event)] + " " + parsed.dump()); if (depth == 1 && event == json::parse_event_t::object_start && first) @@ -2036,7 +2036,7 @@ TEST_CASE("parser class") // further effect") const auto record = [](std::vector& log, int depth, json::parse_event_t event, const json & parsed) { - static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; + static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) log.push_back(std::to_string(depth) + " " + names[static_cast(event)] + " " + parsed.dump()); }; diff --git a/tests/src/unit-constructor2.cpp b/tests/src/unit-constructor2.cpp index 27bdcccd9..d1ff56452 100644 --- a/tests/src/unit-constructor2.cpp +++ b/tests/src/unit-constructor2.cpp @@ -85,7 +85,7 @@ TEST_CASE("other constructors and destructor") CHECK(j.type() == json::value_t::object); const json k(std::move(j)); CHECK(k.type() == json::value_t::object); - CHECK(j.type() == json::value_t::null); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) access after move is OK here + CHECK(j.type() == json::value_t::null); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) access after move is OK here } SECTION("copy assignment") diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index 99a1140cf..ff6e5e5c7 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -1571,9 +1571,15 @@ TEST_CASE("value conversion") json const j7 = {0, 1, 2, 3}; json const j8 = 2; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] (/0) type must be array, " + "but is number", json::type_error&); +#else CHECK_THROWS_WITH_AS((j7.get>()), "[json.exception.type_error.302] type must be array, " "but is number", json::type_error&); +#endif CHECK_THROWS_WITH_AS((j8.get>()), "[json.exception.type_error.302] type must be array, " "but is number", json::type_error&); @@ -1596,9 +1602,15 @@ TEST_CASE("value conversion") json const j7 = {0, 1, 2, 3}; json const j8 = 2; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] (/0) type must be array, " + "but is number", json::type_error&); +#else CHECK_THROWS_WITH_AS((j7.get>()), "[json.exception.type_error.302] type must be array, " "but is number", json::type_error&); +#endif CHECK_THROWS_WITH_AS((j8.get>()), "[json.exception.type_error.302] type must be array, " "but is number", json::type_error&); @@ -1747,10 +1759,10 @@ TEST_CASE("Strict JSON to enum mapping") CHECK(json(strict_cards::karo) == "karo"); // json -> enum - CHECK(strict_cards::kreuz == json("kreuz")); - CHECK(strict_cards::pik == json("pik")); - CHECK(strict_cards::herz == json("herz")); - CHECK(strict_cards::karo == json("karo")); + CHECK(json("kreuz").get() == strict_cards::kreuz); + CHECK(json("pik").get() == strict_cards::pik); + CHECK(json("herz").get() == strict_cards::herz); + CHECK(json("karo").get() == strict_cards::karo); // invalid json -> exception thrown json _; @@ -1775,10 +1787,10 @@ TEST_CASE("Strict JSON to enum mapping") CHECK(json(STRICT_TS_INVALID) == json()); // json -> enum - CHECK(STRICT_TS_STOPPED == json("stopped")); - CHECK(STRICT_TS_RUNNING == json("running")); - CHECK(STRICT_TS_COMPLETED == json("completed")); - CHECK(STRICT_TS_INVALID == json()); + CHECK(json("stopped").get() == STRICT_TS_STOPPED); + CHECK(json("running").get() == STRICT_TS_RUNNING); + CHECK(json("completed").get() == STRICT_TS_COMPLETED); + CHECK(json().get() == STRICT_TS_INVALID); // invalid json -> exception thrown json _; @@ -1910,6 +1922,8 @@ TEST_CASE("std::optional") CHECK(json(opt_string) == j_string); CHECK(std::optional(j_string) == opt_string); + // false positive: Infer attributes the destruction of the temporaries above to opt_string + // @infer-ignore USE_AFTER_DELETE } SECTION("bool") @@ -1960,8 +1974,8 @@ TEST_CASE("std::optional") CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); // the conversion is noexcept exactly when converting the contained value is - static_assert(!std::is_nothrow_constructible&>::value, ""); - static_assert(std::is_nothrow_constructible&>::value, ""); + static_assert(!std::is_nothrow_constructible&>::value); + static_assert(std::is_nothrow_constructible&>::value); } #endif } diff --git a/tests/src/unit-custom-base-class.cpp b/tests/src/unit-custom-base-class.cpp index a7f466e36..138940d3d 100644 --- a/tests/src/unit-custom-base-class.cpp +++ b/tests/src/unit-custom-base-class.cpp @@ -234,7 +234,7 @@ TEST_CASE("JSON Node Metadata") // travel with it, just as it does for copy, move, and assignment using json = json_with_metadata; std::vector values; - for (int v : + for (const int v : { 5, 3, 9, 1, 7, 2, 8, 4, 6, 0, 15, 13, 19, 11, 17, 12, 18, 14, 16, 10, 25, 23, 29, 21, 27, 22, 28, 24, 26, 20, 35, 33 diff --git a/tests/src/unit-diagnostics.cpp b/tests/src/unit-diagnostics.cpp index 62385551c..1ada023b7 100644 --- a/tests/src/unit-diagnostics.cpp +++ b/tests/src/unit-diagnostics.cpp @@ -77,6 +77,8 @@ TEST_CASE("Better diagnostics") SECTION("Parse error") { json _; + // false positive: a default-constructed json is a valid null value + // @infer-ignore NULLPTR_DEREFERENCE CHECK_THROWS_WITH_AS(_ = json::parse(""), "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error); } diff --git a/tests/src/unit-locale-cpp.cpp b/tests/src/unit-locale-cpp.cpp index 3b9593a20..626296825 100644 --- a/tests/src/unit-locale-cpp.cpp +++ b/tests/src/unit-locale-cpp.cpp @@ -406,6 +406,7 @@ struct LocaleSwitchingStreambuf final : std::streambuf std::string locale_after_first_write; bool switched = false; + protected: std::streamsize xsputn(const char* s, std::streamsize n) override { if (!switched) diff --git a/tests/src/unit-noexcept.cpp b/tests/src/unit-noexcept.cpp index 5e330c0a1..637915f24 100644 --- a/tests/src/unit-noexcept.cpp +++ b/tests/src/unit-noexcept.cpp @@ -60,9 +60,9 @@ TEST_CASE("noexcept") { // silence -Wunneeded-internal-declaration errors static_cast(static_cast(&to_json)); - static_cast(static_cast(&to_json)); + static_cast(static_cast(&to_json)); // NOLINT(readability-redundant-casting): selects the overload static_cast(static_cast(&from_json)); - static_cast(static_cast(&from_json)); + static_cast(static_cast(&from_json)); // NOLINT(readability-redundant-casting): selects the overload SECTION("nothrow-copy-constructible exceptions") { diff --git a/tests/src/unit-pointer_access.cpp b/tests/src/unit-pointer_access.cpp index c1e341917..33a4e2047 100644 --- a/tests/src/unit-pointer_access.cpp +++ b/tests/src/unit-pointer_access.cpp @@ -79,6 +79,8 @@ TEST_CASE("pointer access") // check if pointers are returned correctly const test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // false positive: p1 is non-null, as value has type test_type + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); @@ -108,6 +110,8 @@ TEST_CASE("pointer access") // check if pointers are returned correctly test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // false positive: p1 is non-null, as value has type test_type + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); diff --git a/tests/src/unit-udt.cpp b/tests/src/unit-udt.cpp index 24f763f96..5ad72b208 100644 --- a/tests/src/unit-udt.cpp +++ b/tests/src/unit-udt.cpp @@ -446,6 +446,8 @@ TEST_CASE("adl_serializer specialization" * doctest::test_suite("udt")) auto optPerson = j.get>(); REQUIRE(optPerson); + // false positive: REQUIRE above guarantees optPerson is non-null + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*optPerson == person); j = nullptr; @@ -559,6 +561,8 @@ TEST_CASE("Non-copyable types" * doctest::test_suite("udt")) auto optPerson = j.get>(); REQUIRE(optPerson); + // false positive: REQUIRE above guarantees optPerson is non-null + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*optPerson == person); j = nullptr;