From d254f6276493994f457a80988a484f2af380f891 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 30 Sep 2026 18:08:45 +0200 Subject: [PATCH] Move templated_json_throw into nlohmann::detail templated_json_throw() was defined in macro_scope.hpp, which is included outside NLOHMANN_JSON_NAMESPACE_BEGIN, so the helper leaked into the global namespace as ::templated_json_throw with no ABI tag. Unqualified lookup in NLOHMANN_JSON_SERIALIZE_ENUM_STRICT could then bind to a same-named function declared in the user's own namespace instead, which fails to compile with Clang ("does not name a template"). Move the helper next to the exception classes in exceptions.hpp, inside nlohmann::detail, and call it qualified as ::nlohmann::detail::templated_json_throw<...>(...) from both macro expansion sites. Rewrite the doc comment to give the real reason for the helper (JSON_THROW may expand to code that discards its argument, e.g. when exceptions are disabled) and fix the "supress" typo. templated_json_throw was never released (added by #5151 after v3.12.0), so it can be moved freely. Adds a regression test that expands NLOHMANN_JSON_SERIALIZE_ENUM_STRICT inside a namespace declaring its own templated_json_throw. Public API: no change (::templated_json_throw was an unreleased, unintentional global-namespace leak with no callers relying on its location). Overlaps #5698, which rewrites the same two macro call lines; the overlapping hunks are small and should be trivial to reconcile on rebase. Signed-off-by: Niels Lohmann #5708 item 2 --- include/nlohmann/detail/exceptions.hpp | 21 +++++++++++++ include/nlohmann/detail/macro_scope.hpp | 19 ++---------- single_include/nlohmann/json.hpp | 40 ++++++++++++++----------- tests/src/unit-conversions.cpp | 28 +++++++++++++++++ 4 files changed, 74 insertions(+), 34 deletions(-) diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index 808a3b6bf..155f4a4e5 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -286,6 +286,27 @@ class other_error : public exception other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; +/*! +@brief helper function to call JSON_THROW from a template +@note JSON_THROW is a macro that, depending on the JSON_THROW_USER / + JSON_TRY_USER / JSON_NOEXCEPTION configuration, may expand to code + that does not reference its argument (e.g. `std::abort()`), which + would trigger a compilation error if the argument's type depends on + a template parameter that is otherwise unused. Wrapping the call in + a templated function avoids this and gives the compiler a single + place to see the (possibly unused) parameter. +*/ +template +void templated_json_throw(ExceptionType exception) +{ + JSON_THROW(exception); + + // JSON_THROW may expand to code that discards its argument (e.g. when + // exceptions are disabled) - the cast below avoids an unused-parameter + // warning with -Werror in that case + (void)exception; +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 328422dd0..99512fa82 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -287,21 +287,6 @@ -/*! -@brief function to wrap JSON_THROW_MACRO - there can be compilation errors about - there being no arguments to JSON_THROW that depend on template arguments - if this is not used to call JSON_THROW -*/ -template -void templated_json_throw(ExceptionType exception) -{ - JSON_THROW(exception); - - /* JSON_THROW(exception) discards exception and aborts - void cast needed to supress - compilation error if compiled with -Werror and Wunused-parameter */ - (void)exception; -} - /*! @brief macro to briefly define a mapping between an enum and JSON with exception on invalid input @@ -322,7 +307,7 @@ void templated_json_throw(ExceptionType exception) return ej_pair.first == e; \ }); \ if (it != std::end(m)) j = it->second; \ - else templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ @@ -337,7 +322,7 @@ void templated_json_throw(ExceptionType exception) return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE ": " + j.dump(), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE ": " + j.dump(), &j)); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 2e4d948f7..73c70c5e9 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2601,21 +2601,6 @@ JSON_HEDLEY_DIAGNOSTIC_POP -/*! -@brief function to wrap JSON_THROW_MACRO - there can be compilation errors about - there being no arguments to JSON_THROW that depend on template arguments - if this is not used to call JSON_THROW -*/ -template -void templated_json_throw(ExceptionType exception) -{ - JSON_THROW(exception); - - /* JSON_THROW(exception) discards exception and aborts - void cast needed to supress - compilation error if compiled with -Werror and Wunused-parameter */ - (void)exception; -} - /*! @brief macro to briefly define a mapping between an enum and JSON with exception on invalid input @@ -2636,7 +2621,7 @@ void templated_json_throw(ExceptionType exception) return ej_pair.first == e; \ }); \ if (it != std::end(m)) j = it->second; \ - else templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ @@ -2651,7 +2636,7 @@ void templated_json_throw(ExceptionType exception) return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE ": " + j.dump(), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE ": " + j.dump(), &j)); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -5360,6 +5345,27 @@ class other_error : public exception other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; +/*! +@brief helper function to call JSON_THROW from a template +@note JSON_THROW is a macro that, depending on the JSON_THROW_USER / + JSON_TRY_USER / JSON_NOEXCEPTION configuration, may expand to code + that does not reference its argument (e.g. `std::abort()`), which + would trigger a compilation error if the argument's type depends on + a template parameter that is otherwise unused. Wrapping the call in + a templated function avoids this and gives the compiler a single + place to see the (possibly unused) parameter. +*/ +template +void templated_json_throw(ExceptionType exception) +{ + JSON_THROW(exception); + + // JSON_THROW may expand to code that discards its argument (e.g. when + // exceptions are disabled) - the cast below avoids an unused-parameter + // warning with -Werror in that case + (void)exception; +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index a729a69e8..58b9100f6 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -1749,6 +1749,34 @@ NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState, {STRICT_TS_COMPLETED, "completed"}, }) +// regression test for #5708 item 2: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT must not rely on +// unqualified lookup of a helper name that a user's own namespace may also declare +namespace ns_with_colliding_name +{ +// NOLINTNEXTLINE(misc-use-internal-linkage) - used to shadow the library's internal helper name +inline void templated_json_throw(int /*unused*/) {} + +enum class colliding_enum { a, b }; + +// 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(colliding_enum, +{ + {colliding_enum::a, "a"}, + {colliding_enum::b, "b"} +}) +} // namespace ns_with_colliding_name + +TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT in a namespace with a colliding name") +{ + using ns_with_colliding_name::colliding_enum; + + CHECK(json(colliding_enum::a) == "a"); + CHECK(colliding_enum::b == json("b")); + + json _; + CHECK_THROWS_WITH_AS(_ = json("nope").get(), "[json.exception.out_of_range.410] enum value out of range for colliding_enum: \"nope\"", json::out_of_range&); +} + TEST_CASE("Strict JSON to enum mapping") { SECTION("enum class")