From 8aff27a0f9d1b7cf5878e68e7b8f6abf0c07da45 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 05:51:08 +0000 Subject: [PATCH] Fix CI for the parse() bad_alloc sweep test (#5985) - sweep_allocator: provide max_size(), construct() and destroy(), which GCC 4.8's std::map calls on the allocator directly. - Declare the non-throwing test lambdas noexcept (-Werror=noexcept on recent GCC). - Give the sax_parse lambda an explicit return type; Clang 3.4 crashes on the deduced one in C++11 mode. - Skip the parser callback sweep with iterator debugging (MSVC debug builds, clang-cl), where containers allocate debug proxies inside noexcept move constructors and terminate instead of throwing, as in the other allocator tests. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude --- tests/src/unit-allocator.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index 96d13b1d3..1bb68e41f 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -12,6 +12,7 @@ #include using nlohmann::json; +#include #include #if JSON_HAS_RANGES #include @@ -790,7 +791,7 @@ TEST_CASE("a failed allocation leaves the value unchanged") #if JSON_HAS_RANGES && !defined(__MINGW32__) const std::vector numbers = {1, 2}; next_construct_fails = true; - CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/) + CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/) noexcept { return true; })), std::bad_alloc&); @@ -837,6 +838,26 @@ struct sweep_allocator std::allocator().deallocate(p, n); } + // GCC 4.8's std::map calls these on the allocator directly instead of + // going through std::allocator_traits + std::size_t max_size() const noexcept + { + return (std::numeric_limits::max)() / sizeof(T); + } + + template + void construct(U* p, Args&& ... args) noexcept(noexcept(::new (static_cast(p)) U(std::forward(args)...))) + { + ::new (static_cast(p)) U(std::forward(args)...); + } + + template + void destroy(U* p) noexcept + { + static_cast(p); // unused for trivially destructible types + p->~U(); + } + template struct rebind { @@ -912,9 +933,14 @@ TEST_CASE("parse() survives a failing allocation at every point (#5985)") }); } + // 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("parser callback") { - const auto keep_all = [](int /*depth*/, sweep_json::parse_event_t /*event*/, sweep_json& /*parsed*/) + const auto keep_all = [](int /*depth*/, sweep_json::parse_event_t /*event*/, sweep_json& /*parsed*/) noexcept { return true; }; @@ -932,12 +958,13 @@ TEST_CASE("parse() survives a failing allocation at every point (#5985)") return sweep_json::parse(text, drop_d); }); } +#endif SECTION("sax_parse into an existing value") { // a failed parse leaves the target in a state that is safe to destroy using sax_t = nlohmann::detail::json_sax_dom_parser>; - sweep_allocations_of([&] + sweep_allocations_of([&]() -> sweep_json { sweep_json value; sax_t sax(value, false);