diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 576ba62cc..247c5babb 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -748,6 +748,20 @@ class json_pointer } } + // the reference token consists only of digits at this point (cf. checks + // above); however, its numeric value might not be representable, in which + // case array_index() would throw out_of_range.404/410 -- contains() must + // not throw (see #5395), so such a reference token is treated as "not found" + errno = 0; // strtoull() does not reset errno on success + char* p_end = nullptr; // NOLINT(misc-const-correctness) + const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int) + if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX + || magnitude >= static_cast((std::numeric_limits::max)()))) // NOLINT(runtime/int) + { + // the array index cannot be represented as size_type + return false; + } + const auto idx = array_index(reference_token); if (idx >= ptr->size()) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 31bfb869d..a8fff3e41 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -16394,6 +16394,20 @@ class json_pointer } } + // the reference token consists only of digits at this point (cf. checks + // above); however, its numeric value might not be representable, in which + // case array_index() would throw out_of_range.404/410 -- contains() must + // not throw (see #5395), so such a reference token is treated as "not found" + errno = 0; // strtoull() does not reset errno on success + char* p_end = nullptr; // NOLINT(misc-const-correctness) + const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int) + if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX + || magnitude >= static_cast((std::numeric_limits::max)()))) // NOLINT(runtime/int) + { + // the array index cannot be represented as size_type + return false; + } + const auto idx = array_index(reference_token); if (idx >= ptr->size()) { diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index a8ed4a89e..4082de45c 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -319,6 +319,44 @@ TEST_CASE("JSON pointers") CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&); CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&); + + // #5395: contains() must not throw for a reference token that is a + // syntactically valid array index but numerically exceeds ULLONG_MAX + // (causing strtoull() to set errno to ERANGE) -- it should just report + // that the pointer does not resolve to an element + CHECK(!j.contains(jp)); + CHECK(!j_const.contains(jp)); + } + + { + // #5395: same as above, but using the exact reproduction from the issue + json::json_pointer const jp("/99999999999999999999"); + std::string const throw_msg = "[json.exception.out_of_range.404] unresolved reference token '99999999999999999999'"; + + CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(j.at(jp) = 1, throw_msg.c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(j_const.at(jp) == 1, throw_msg.c_str(), json::out_of_range&); + + CHECK(!j.contains(jp)); + CHECK(!j_const.contains(jp)); + } + + { + // #5395: a reference token that is numerically representable in + // unsigned long long but exceeds size_type's max (e.g. ULLONG_MAX + // itself on typical 64-bit platforms, where size_type's max equals + // ULLONG_MAX) must not make contains() throw either + json::json_pointer const jp("/18446744073709551615"); + std::string const throw_msg = "[json.exception.out_of_range.410] array index 18446744073709551615 exceeds size_type"; + + CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(j.at(jp) = 1, throw_msg.c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(j_const.at(jp) == 1, throw_msg.c_str(), json::out_of_range&); + + CHECK(!j.contains(jp)); + CHECK(!j_const.contains(jp)); } // on some machines, the check below is not constant @@ -334,6 +372,10 @@ TEST_CASE("JSON pointers") CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&); CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&); + + // #5395: contains() must not throw for a reference token exceeding size_type's max + CHECK(!j.contains(jp)); + CHECK(!j_const.contains(jp)); } DOCTEST_MSVC_SUPPRESS_WARNING_POP